Path: blob/master/src/java.desktop/unix/classes/sun/font/FcFontConfiguration.java
66645 views
/*1* Copyright (c) 2008, 2022, 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}295296private String extractOsInfo(String s) {297if (s.startsWith("\"")) s = s.substring(1);298if (s.endsWith("\"")) s = s.substring(0, s.length()-1);299return s;300}301302/**303* Sets the OS name and version from environment information.304*/305@Override306protected void setOsNameAndVersion() {307308super.setOsNameAndVersion();309310if (!osName.equals("Linux")) {311return;312}313try {314File f;315if ((f = new File("/etc/lsb-release")).canRead()) {316/* Ubuntu and (perhaps others) use only lsb-release.317* Syntax and encoding is compatible with java properties.318* For Ubuntu the ID is "Ubuntu".319*/320Properties props = new Properties();321props.load(new FileInputStream(f));322osName = props.getProperty("DISTRIB_ID");323osVersion = props.getProperty("DISTRIB_RELEASE");324} else if ((f = new File("/etc/redhat-release")).canRead()) {325osName = "RedHat";326osVersion = getVersionString(f);327} else if ((f = new File("/etc/SuSE-release")).canRead()) {328osName = "SuSE";329osVersion = getVersionString(f);330} else if ((f = new File("/etc/turbolinux-release")).canRead()) {331osName = "Turbo";332osVersion = getVersionString(f);333} else if ((f = new File("/etc/fedora-release")).canRead()) {334osName = "Fedora";335osVersion = getVersionString(f);336} else if ((f = new File("/etc/os-release")).canRead()) {337Properties props = new Properties();338try (FileInputStream fis = new FileInputStream(f)) {339props.load(fis);340}341osName = props.getProperty("NAME");342osVersion = props.getProperty("VERSION_ID");343osName = extractOsInfo(osName);344if (osName.equals("SLES")) osName = "SuSE";345osVersion = extractOsInfo(osVersion);346}347} catch (Exception e) {348if (FontUtilities.debugFonts()) {349warning("Exception identifying Linux distro.");350}351}352}353354private File getFcInfoFile() {355if (fcInfoFileName == null) {356// NB need security permissions to get true IP address, and357// we should have those as the whole initialisation is in a358// doPrivileged block. But in this case no exception is thrown,359// and it returns the loop back address, and so we end up with360// "localhost"361String hostname;362try {363hostname = InetAddress.getLocalHost().getHostName();364} catch (UnknownHostException e) {365hostname = "localhost";366}367String userDir = System.getProperty("user.home");368String version = System.getProperty("java.version");369String fs = File.separator;370String dir = userDir+fs+".java"+fs+"fonts"+fs+version;371Locale locale = SunToolkit.getStartupLocale();372String lang = locale.getLanguage();373String country = locale.getCountry();374String name = "fcinfo-"+fileVersion+"-"+hostname+"-"+375osName+"-"+osVersion+"-"+lang+"-"+country+".properties";376fcInfoFileName = dir+fs+name;377}378return new File(fcInfoFileName);379}380381private void writeFcInfo() {382Properties props = new Properties();383props.setProperty("version", fileVersion);384FcFontManager fm = (FcFontManager) fontManager;385FontConfigManager fcm = fm.getFontConfigManager();386FontConfigInfo fcInfo = fcm.getFontConfigInfo();387props.setProperty("fcversion", Integer.toString(fcInfo.fcVersion));388if (fcInfo.cacheDirs != null) {389for (int i=0;i<fcInfo.cacheDirs.length;i++) {390if (fcInfo.cacheDirs[i] != null) {391props.setProperty("cachedir."+i, fcInfo.cacheDirs[i]);392}393}394}395for (int i=0; i<fcCompFonts.length; i++) {396FcCompFont fci = fcCompFonts[i];397String styleKey = fci.jdkName+"."+fci.style;398props.setProperty(styleKey+".length",399Integer.toString(fci.allFonts.length));400for (int j=0; j<fci.allFonts.length; j++) {401props.setProperty(styleKey+"."+j+".file",402fci.allFonts[j].fontFile);403if (fci.allFonts[j].fullName != null) {404props.setProperty(styleKey+"."+j+".fullName",405fci.allFonts[j].fullName);406}407}408}409try {410/* This writes into a temp file then renames when done.411* Since the rename is an atomic action within the same412* directory no client will ever see a partially written file.413*/414File fcInfoFile = getFcInfoFile();415File dir = fcInfoFile.getParentFile();416dir.mkdirs();417File tempFile = Files.createTempFile(dir.toPath(), "fcinfo", null).toFile();418FileOutputStream fos = new FileOutputStream(tempFile);419props.store(fos,420"JDK Font Configuration Generated File: *Do Not Edit*");421fos.close();422boolean renamed = tempFile.renameTo(fcInfoFile);423if (!renamed && FontUtilities.debugFonts()) {424System.out.println("rename failed");425warning("Failed renaming file to "+ getFcInfoFile());426}427} catch (Exception e) {428if (FontUtilities.debugFonts()) {429warning("IOException writing to "+ getFcInfoFile());430}431}432}433434/* We want to be able to use this cache instead of invoking435* fontconfig except when we can detect the system cache has changed.436* But there doesn't seem to be a way to find the location of437* the system cache.438*/439private void readFcInfo() {440File fcFile = getFcInfoFile();441if (!fcFile.exists()) {442if (FontUtilities.debugFonts()) {443warning("fontconfig info file " + fcFile.toString() + " does not exist");444}445return;446}447Properties props = new Properties();448try (FileInputStream fis = new FileInputStream(fcFile)) {449props.load(fis);450} catch (IOException e) {451if (FontUtilities.debugFonts()) {452warning("IOException (" + e.getCause() + ") reading from " + fcFile.toString());453}454return;455}456String version = (String)props.get("version");457if (version == null || !version.equals(fileVersion)) {458if (FontUtilities.debugFonts()) {459warning("fontconfig info file version mismatch (found: " + version +460", expected: " + fileVersion + ")");461}462return;463}464465// If there's a new, different fontconfig installed on the466// system, we invalidate our fontconfig file.467String fcVersionStr = (String)props.get("fcversion");468if (fcVersionStr != null) {469int fcVersion;470try {471fcVersion = Integer.parseInt(fcVersionStr);472if (fcVersion != 0 &&473fcVersion != FontConfigManager.getFontConfigVersion()) {474if (FontUtilities.debugFonts()) {475warning("new, different fontconfig detected");476}477return;478}479} catch (Exception e) {480if (FontUtilities.debugFonts()) {481warning("Exception parsing version " + fcVersionStr);482}483return;484}485}486487// If we can locate the fontconfig cache dirs, then compare the488// time stamp of those with our properties file. If we are out489// of date then re-generate.490long lastModified = fcFile.lastModified();491int cacheDirIndex = 0;492while (cacheDirIndex<4) { // should never be more than 2 anyway.493String dir = (String)props.get("cachedir."+cacheDirIndex);494if (dir == null) {495break;496}497File dirFile = new File(dir);498if (dirFile.exists() && dirFile.lastModified() > lastModified) {499if (FontUtilities.debugFonts()) {500warning("out of date cache directories detected");501}502return;503}504cacheDirIndex++;505}506507String[] names = { "sansserif", "serif", "monospaced" };508String[] fcnames = { "sans", "serif", "monospace" };509int namesLen = names.length;510int numStyles = 4;511FcCompFont[] fci = new FcCompFont[namesLen*numStyles];512513try {514for (int i=0; i<namesLen; i++) {515for (int s=0; s<numStyles; s++) {516int index = i*numStyles+s;517fci[index] = new FcCompFont();518String key = names[i]+"."+s;519fci[index].jdkName = names[i];520fci[index].fcFamily = fcnames[i];521fci[index].style = s;522String lenStr = (String)props.get(key+".length");523int nfonts = Integer.parseInt(lenStr);524if (nfonts <= 0) {525if (FontUtilities.debugFonts()) {526warning("bad non-positive .length entry in fontconfig file " + fcFile.toString());527}528return; // bad file529}530fci[index].allFonts = new FontConfigFont[nfonts];531for (int f=0; f<nfonts; f++) {532fci[index].allFonts[f] = new FontConfigFont();533String fkey = key+"."+f+".fullName";534String fullName = (String)props.get(fkey);535fci[index].allFonts[f].fullName = fullName;536fkey = key+"."+f+".file";537String file = (String)props.get(fkey);538if (file == null) {539if (FontUtilities.debugFonts()) {540warning("missing file value for key " + fkey + " in fontconfig file " + fcFile.toString());541}542return; // bad file543}544fci[index].allFonts[f].fontFile = file;545}546fci[index].firstFont = fci[index].allFonts[0];547548}549}550fcCompFonts = fci;551} catch (Throwable t) {552if (FontUtilities.debugFonts()) {553warning(t.toString());554}555}556557if (FontUtilities.debugFonts()) {558FontUtilities.logInfo("successfully parsed the fontconfig file at " + fcFile.toString());559}560}561562private static void warning(String msg) {563PlatformLogger logger = PlatformLogger.getLogger("sun.awt.FontConfiguration");564logger.warning(msg);565}566}567568569