Path: blob/master/src/java.desktop/unix/classes/sun/font/FcFontConfiguration.java
41153 views
/*1* Copyright (c) 2008, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.font;2627import java.io.File;28import java.io.FileInputStream;29import java.io.FileOutputStream;30import java.io.IOException;31import java.net.InetAddress;32import java.net.UnknownHostException;33import java.nio.charset.Charset;34import java.nio.charset.StandardCharsets;35import java.nio.file.Files;36import java.util.HashMap;37import java.util.HashSet;38import java.util.Locale;39import java.util.Properties;40import java.util.Scanner;41import sun.awt.FcFontManager;42import sun.awt.FontConfiguration;43import sun.awt.FontDescriptor;44import sun.awt.SunToolkit;45import sun.font.CompositeFontDescriptor;46import sun.font.FontConfigManager.FontConfigInfo;47import sun.font.FontConfigManager.FcCompFont;48import sun.font.FontConfigManager.FontConfigFont;49import sun.util.logging.PlatformLogger;5051public class FcFontConfiguration extends FontConfiguration {5253/** Version of the cache file format understood by this code.54* Its part of the file name so that we can rev this at55* 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 generated58* 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 file61* that was read. Since this class doesn't use such files, then what62* that really means is whether the methods on this class return63* values that are compatible with the classes that do directly read64* from such files. It is a compatible subset of version "1".65*/66private static final String fileVersion = "1";67private String fcInfoFileName = null;6869private FcCompFont[] fcCompFonts = null;7071public FcFontConfiguration(SunFontManager fm) {72super(fm);73init();74}7576/* This isn't called but is needed to satisfy super-class contract. */77public FcFontConfiguration(SunFontManager fm,78boolean preferLocaleFonts,79boolean preferPropFonts) {80super(fm, preferLocaleFonts, preferPropFonts);81init();82}8384@Override85public synchronized boolean init() {86if (fcCompFonts != null) {87return true;88}8990setFontConfiguration();91readFcInfo();92FcFontManager fm = (FcFontManager) fontManager;93FontConfigManager fcm = fm.getFontConfigManager();94if (fcCompFonts == null) {95fcCompFonts = fcm.loadFontConfig();96if (fcCompFonts != null) {97try {98writeFcInfo();99} catch (Exception e) {100if (FontUtilities.debugFonts()) {101warning("Exception writing fcInfo " + e);102}103}104} else if (FontUtilities.debugFonts()) {105warning("Failed to get info from libfontconfig");106}107} else {108fcm.populateFontConfig(fcCompFonts);109}110111if (fcCompFonts == null) {112return false; // couldn't load fontconfig.113}114115// NB already in a privileged block from SGE116String javaHome = System.getProperty("java.home");117if (javaHome == null) {118throw new Error("java.home property not set");119}120String javaLib = javaHome + File.separator + "lib";121getInstalledFallbackFonts(javaLib);122123return true;124}125126@Override127public String getFallbackFamilyName(String fontName,128String defaultFallback) {129// maintain compatibility with old font.properties files, which either130// had aliases for TimesRoman & Co. or defined mappings for them.131String compatibilityName = getCompatibilityFamilyName(fontName);132if (compatibilityName != null) {133return compatibilityName;134}135return defaultFallback;136}137138@Override139protected String140getFaceNameFromComponentFontName(String componentFontName) {141return null;142}143144@Override145protected String146getFileNameFromComponentFontName(String componentFontName) {147return null;148}149150@Override151public String getFileNameFromPlatformName(String platformName) {152/* Platform name is the file name, but rather than returning153* the arg, return null*/154return null;155}156157@Override158protected Charset getDefaultFontCharset(String fontName) {159return Charset.forName("ISO8859_1");160}161162@Override163protected String getEncoding(String awtFontName,164String characterSubsetName) {165return "default";166}167168@Override169protected void initReorderMap() {170reorderMap = new HashMap<>();171}172173@Override174protected FontDescriptor[] buildFontDescriptors(int fontIndex, int styleIndex) {175CompositeFontDescriptor[] cfi = get2DCompositeFontInfo();176int idx = fontIndex * NUM_STYLES + styleIndex;177String[] componentFaceNames = cfi[idx].getComponentFaceNames();178FontDescriptor[] ret = new FontDescriptor[componentFaceNames.length];179for (int i = 0; i < componentFaceNames.length; i++) {180ret[i] = new FontDescriptor(componentFaceNames[i], StandardCharsets.ISO_8859_1.newEncoder(), new int[0]);181}182183return ret;184}185186@Override187public int getNumberCoreFonts() {188return 1;189}190191@Override192public String[] getPlatformFontNames() {193HashSet<String> nameSet = new HashSet<String>();194FcFontManager fm = (FcFontManager) fontManager;195FontConfigManager fcm = fm.getFontConfigManager();196FcCompFont[] fcCompFonts = fcm.loadFontConfig();197for (int i=0; i<fcCompFonts.length; i++) {198for (int j=0; j<fcCompFonts[i].allFonts.length; j++) {199nameSet.add(fcCompFonts[i].allFonts[j].fontFile);200}201}202return nameSet.toArray(new String[0]);203}204205@Override206public String getExtraFontPath() {207return null;208}209210@Override211public boolean needToSearchForFile(String fileName) {212return false;213}214215private FontConfigFont[] getFcFontList(FcCompFont[] fcFonts,216String fontname, int style) {217218if (fontname.equals("dialog")) {219fontname = "sansserif";220} else if (fontname.equals("dialoginput")) {221fontname = "monospaced";222}223for (int i=0; i<fcFonts.length; i++) {224if (fontname.equals(fcFonts[i].jdkName) &&225style == fcFonts[i].style) {226return fcFonts[i].allFonts;227}228}229return fcFonts[0].allFonts;230}231232@Override233public CompositeFontDescriptor[] get2DCompositeFontInfo() {234235FcFontManager fm = (FcFontManager) fontManager;236FontConfigManager fcm = fm.getFontConfigManager();237FcCompFont[] fcCompFonts = fcm.loadFontConfig();238239CompositeFontDescriptor[] result =240new CompositeFontDescriptor[NUM_FONTS * NUM_STYLES];241242for (int fontIndex = 0; fontIndex < NUM_FONTS; fontIndex++) {243String fontName = publicFontNames[fontIndex];244245for (int styleIndex = 0; styleIndex < NUM_STYLES; styleIndex++) {246247String faceName = fontName + "." + styleNames[styleIndex];248FontConfigFont[] fcFonts =249getFcFontList(fcCompFonts,250fontNames[fontIndex], styleIndex);251252int numFonts = fcFonts.length;253// fall back fonts listed in the lib/fonts/fallback directory254if (installedFallbackFontFiles != null) {255numFonts += installedFallbackFontFiles.length;256}257258String[] fileNames = new String[numFonts];259String[] faceNames = new String[numFonts];260261int index;262for (index = 0; index < fcFonts.length; index++) {263fileNames[index] = fcFonts[index].fontFile;264faceNames[index] = fcFonts[index].fullName;265}266267if (installedFallbackFontFiles != null) {268System.arraycopy(installedFallbackFontFiles, 0,269fileNames, fcFonts.length,270installedFallbackFontFiles.length);271}272273result[fontIndex * NUM_STYLES + styleIndex]274= new CompositeFontDescriptor(275faceName,2761,277faceNames,278fileNames,279null, null);280}281}282return result;283}284285/**286* Gets the OS version string from a Linux release-specific file.287*/288private String getVersionString(File f) {289try (Scanner sc = new Scanner(f)) {290return sc.findInLine("(\\d)+((\\.)(\\d)+)*");291} catch (Exception e) {292}293return null;294}295296/**297* Sets the OS name and version from environment information.298*/299@Override300protected void setOsNameAndVersion() {301302super.setOsNameAndVersion();303304if (!osName.equals("Linux")) {305return;306}307try {308File f;309if ((f = new File("/etc/lsb-release")).canRead()) {310/* Ubuntu and (perhaps others) use only lsb-release.311* Syntax and encoding is compatible with java properties.312* For Ubuntu the ID is "Ubuntu".313*/314Properties props = new Properties();315props.load(new FileInputStream(f));316osName = props.getProperty("DISTRIB_ID");317osVersion = props.getProperty("DISTRIB_RELEASE");318} else if ((f = new File("/etc/redhat-release")).canRead()) {319osName = "RedHat";320osVersion = getVersionString(f);321} else if ((f = new File("/etc/SuSE-release")).canRead()) {322osName = "SuSE";323osVersion = getVersionString(f);324} else if ((f = new File("/etc/turbolinux-release")).canRead()) {325osName = "Turbo";326osVersion = getVersionString(f);327} else if ((f = new File("/etc/fedora-release")).canRead()) {328osName = "Fedora";329osVersion = getVersionString(f);330}331} catch (Exception e) {332if (FontUtilities.debugFonts()) {333warning("Exception identifying Linux distro.");334}335}336}337338private File getFcInfoFile() {339if (fcInfoFileName == null) {340// NB need security permissions to get true IP address, and341// we should have those as the whole initialisation is in a342// doPrivileged block. But in this case no exception is thrown,343// and it returns the loop back address, and so we end up with344// "localhost"345String hostname;346try {347hostname = InetAddress.getLocalHost().getHostName();348} catch (UnknownHostException e) {349hostname = "localhost";350}351String userDir = System.getProperty("user.home");352String version = System.getProperty("java.version");353String fs = File.separator;354String dir = userDir+fs+".java"+fs+"fonts"+fs+version;355Locale locale = SunToolkit.getStartupLocale();356String lang = locale.getLanguage();357String country = locale.getCountry();358String name = "fcinfo-"+fileVersion+"-"+hostname+"-"+359osName+"-"+osVersion+"-"+lang+"-"+country+".properties";360fcInfoFileName = dir+fs+name;361}362return new File(fcInfoFileName);363}364365private void writeFcInfo() {366Properties props = new Properties();367props.setProperty("version", fileVersion);368FcFontManager fm = (FcFontManager) fontManager;369FontConfigManager fcm = fm.getFontConfigManager();370FontConfigInfo fcInfo = fcm.getFontConfigInfo();371props.setProperty("fcversion", Integer.toString(fcInfo.fcVersion));372if (fcInfo.cacheDirs != null) {373for (int i=0;i<fcInfo.cacheDirs.length;i++) {374if (fcInfo.cacheDirs[i] != null) {375props.setProperty("cachedir."+i, fcInfo.cacheDirs[i]);376}377}378}379for (int i=0; i<fcCompFonts.length; i++) {380FcCompFont fci = fcCompFonts[i];381String styleKey = fci.jdkName+"."+fci.style;382props.setProperty(styleKey+".length",383Integer.toString(fci.allFonts.length));384for (int j=0; j<fci.allFonts.length; j++) {385props.setProperty(styleKey+"."+j+".file",386fci.allFonts[j].fontFile);387if (fci.allFonts[j].fullName != null) {388props.setProperty(styleKey+"."+j+".fullName",389fci.allFonts[j].fullName);390}391}392}393try {394/* This writes into a temp file then renames when done.395* Since the rename is an atomic action within the same396* directory no client will ever see a partially written file.397*/398File fcInfoFile = getFcInfoFile();399File dir = fcInfoFile.getParentFile();400dir.mkdirs();401File tempFile = Files.createTempFile(dir.toPath(), "fcinfo", null).toFile();402FileOutputStream fos = new FileOutputStream(tempFile);403props.store(fos,404"JDK Font Configuration Generated File: *Do Not Edit*");405fos.close();406boolean renamed = tempFile.renameTo(fcInfoFile);407if (!renamed && FontUtilities.debugFonts()) {408System.out.println("rename failed");409warning("Failed renaming file to "+ getFcInfoFile());410}411} catch (Exception e) {412if (FontUtilities.debugFonts()) {413warning("IOException writing to "+ getFcInfoFile());414}415}416}417418/* We want to be able to use this cache instead of invoking419* fontconfig except when we can detect the system cache has changed.420* But there doesn't seem to be a way to find the location of421* the system cache.422*/423private void readFcInfo() {424File fcFile = getFcInfoFile();425if (!fcFile.exists()) {426if (FontUtilities.debugFonts()) {427warning("fontconfig info file " + fcFile.toString() + " does not exist");428}429return;430}431Properties props = new Properties();432try (FileInputStream fis = new FileInputStream(fcFile)) {433props.load(fis);434} catch (IOException e) {435if (FontUtilities.debugFonts()) {436warning("IOException (" + e.getCause() + ") reading from " + fcFile.toString());437}438return;439}440String version = (String)props.get("version");441if (version == null || !version.equals(fileVersion)) {442if (FontUtilities.debugFonts()) {443warning("fontconfig info file version mismatch (found: " + version +444", expected: " + fileVersion + ")");445}446return;447}448449// If there's a new, different fontconfig installed on the450// system, we invalidate our fontconfig file.451String fcVersionStr = (String)props.get("fcversion");452if (fcVersionStr != null) {453int fcVersion;454try {455fcVersion = Integer.parseInt(fcVersionStr);456if (fcVersion != 0 &&457fcVersion != FontConfigManager.getFontConfigVersion()) {458if (FontUtilities.debugFonts()) {459warning("new, different fontconfig detected");460}461return;462}463} catch (Exception e) {464if (FontUtilities.debugFonts()) {465warning("Exception parsing version " + fcVersionStr);466}467return;468}469}470471// If we can locate the fontconfig cache dirs, then compare the472// time stamp of those with our properties file. If we are out473// of date then re-generate.474long lastModified = fcFile.lastModified();475int cacheDirIndex = 0;476while (cacheDirIndex<4) { // should never be more than 2 anyway.477String dir = (String)props.get("cachedir."+cacheDirIndex);478if (dir == null) {479break;480}481File dirFile = new File(dir);482if (dirFile.exists() && dirFile.lastModified() > lastModified) {483if (FontUtilities.debugFonts()) {484warning("out of date cache directories detected");485}486return;487}488cacheDirIndex++;489}490491String[] names = { "sansserif", "serif", "monospaced" };492String[] fcnames = { "sans", "serif", "monospace" };493int namesLen = names.length;494int numStyles = 4;495FcCompFont[] fci = new FcCompFont[namesLen*numStyles];496497try {498for (int i=0; i<namesLen; i++) {499for (int s=0; s<numStyles; s++) {500int index = i*numStyles+s;501fci[index] = new FcCompFont();502String key = names[i]+"."+s;503fci[index].jdkName = names[i];504fci[index].fcFamily = fcnames[i];505fci[index].style = s;506String lenStr = (String)props.get(key+".length");507int nfonts = Integer.parseInt(lenStr);508if (nfonts <= 0) {509if (FontUtilities.debugFonts()) {510warning("bad non-positive .length entry in fontconfig file " + fcFile.toString());511}512return; // bad file513}514fci[index].allFonts = new FontConfigFont[nfonts];515for (int f=0; f<nfonts; f++) {516fci[index].allFonts[f] = new FontConfigFont();517String fkey = key+"."+f+".fullName";518String fullName = (String)props.get(fkey);519fci[index].allFonts[f].fullName = fullName;520fkey = key+"."+f+".file";521String file = (String)props.get(fkey);522if (file == null) {523if (FontUtilities.debugFonts()) {524warning("missing file value for key " + fkey + " in fontconfig file " + fcFile.toString());525}526return; // bad file527}528fci[index].allFonts[f].fontFile = file;529}530fci[index].firstFont = fci[index].allFonts[0];531532}533}534fcCompFonts = fci;535} catch (Throwable t) {536if (FontUtilities.debugFonts()) {537warning(t.toString());538}539}540541if (FontUtilities.debugFonts()) {542FontUtilities.logInfo("successfully parsed the fontconfig file at " + fcFile.toString());543}544}545546private static void warning(String msg) {547PlatformLogger logger = PlatformLogger.getLogger("sun.awt.FontConfiguration");548logger.warning(msg);549}550}551552553