1 /*
2  * Copyright (c) 2008, 2020, 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.util.Locale;
29 
30 import sun.awt.SunHints;
31 import sun.awt.SunToolkit;
32 import sun.util.logging.PlatformLogger;
33 
34 /**
35  * Small utility class to manage FontConfig.
36  */
37 public class FontConfigManager {
38 
39     static boolean fontConfigFailed = false;
40 
41     /* This is populated by native */
42     private static final FontConfigInfo fcInfo = new FontConfigInfo();
43 
44     /* Begin support for GTK Look and Feel - query libfontconfig and
45      * return a composite Font to Swing that uses the desktop font(s).
46      */
47 
48     /* These next three classes are just data structures.
49      */
50     public static class FontConfigFont {
51         public String familyName;        // eg Bitstream Vera Sans
52         public String styleStr;          // eg Bold
53         public String fullName;          // eg Bitstream Vera Sans Bold
54         public String fontFile;          // eg /usr/X11/lib/fonts/foo.ttf
55     }
56 
57     public static class FcCompFont {
58         public String fcName;            // eg sans
59         public String fcFamily;          // eg sans
60         public String jdkName;           // eg sansserif
61         public int style;                // eg 0=PLAIN
62         public FontConfigFont firstFont;
63         public FontConfigFont[] allFonts;
64         //boolean preferBitmaps;    // if embedded bitmaps preferred over AA
65         public CompositeFont compFont;   // null if not yet created/known.
66     }
67 
68     public static class FontConfigInfo {
69         public int fcVersion;
70         public String[] cacheDirs = new String[4];
71     }
72 
73     /* fontconfig recognises slants roman, italic, as well as oblique,
74      * and a slew of weights, where the ones that matter here are
75      * regular and bold.
76      * To fully qualify what we want, we can for example ask for (eg)
77      * Font.PLAIN             : "serif:regular:roman"
78      * Font.BOLD              : "serif:bold:roman"
79      * Font.ITALIC            : "serif:regular:italic"
80      * Font.BOLD|Font.ITALIC  : "serif:bold:italic"
81      */
82     private static String[] fontConfigNames = {
83         "sans:regular:roman",
84         "sans:bold:roman",
85         "sans:regular:italic",
86         "sans:bold:italic",
87 
88         "serif:regular:roman",
89         "serif:bold:roman",
90         "serif:regular:italic",
91         "serif:bold:italic",
92 
93         "monospace:regular:roman",
94         "monospace:bold:roman",
95         "monospace:regular:italic",
96         "monospace:bold:italic",
97     };
98 
99     /* This array has the array elements created in Java code and is
100      * passed down to native to be filled in.
101      */
102     private FcCompFont[] fontConfigFonts;
103 
104     /**
105      * Instantiates a new FontConfigManager getting the default instance
106      * of FontManager from the FontManagerFactory.
107      */
FontConfigManager()108     public FontConfigManager() {
109     }
110 
111     /* Called from code that needs to know what are the AA settings
112      * that apps using FC would pick up for the default desktop font.
113      * Note apps can change the default desktop font. etc, so this
114      * isn't certain to be right but its going to correct for most cases.
115      * Native return values map to the text aa values in sun.awt.SunHints.
116      * which is used to look up the renderinghint value object.
117      */
getFontConfigAAHint()118     public static Object getFontConfigAAHint() {
119         return getFontConfigAAHint("sans");
120     }
121 
122     /* This is public solely so that for debugging purposes it can be called
123      * with other names, which might (eg) include a size, eg "sans-24"
124      * The return value is a text aa rendering hint value.
125      * Normally we should call the no-args version.
126      */
getFontConfigAAHint(String fcFamily)127     public static Object getFontConfigAAHint(String fcFamily) {
128         if (FontUtilities.isWindows) {
129             return null;
130         } else {
131             int hint = getFontConfigAASettings(getFCLocaleStr(), fcFamily);
132             if (hint < 0) {
133                 return null;
134             } else {
135                 return SunHints.Value.get(SunHints.INTKEY_TEXT_ANTIALIASING,
136                                           hint);
137             }
138         }
139     }
140 
141 
getFCLocaleStr()142     private static String getFCLocaleStr() {
143         Locale l = SunToolkit.getStartupLocale();
144         String localeStr = l.getLanguage();
145         String country = l.getCountry();
146         if (!country.isEmpty()) {
147             localeStr = localeStr + "-" + country;
148         }
149         return localeStr;
150     }
151 
152     /* This does cause the native libfontconfig to be loaded and unloaded,
153      * but it does not incur the overhead of initialisation of its
154      * data structures, so shouldn't have a measurable impact.
155      */
getFontConfigVersion()156     public static native int getFontConfigVersion();
157 
158     /* This can be made public if it's needed to force a re-read
159      * rather than using the cached values. The re-read would be needed
160      * only if some event signalled that the fontconfig has changed.
161      * In that event this method would need to return directly the array
162      * to be used by the caller in case it subsequently changed.
163      */
initFontConfigFonts(boolean includeFallbacks)164     public synchronized void initFontConfigFonts(boolean includeFallbacks) {
165 
166         if (fontConfigFonts != null) {
167             if (!includeFallbacks || (fontConfigFonts[0].allFonts != null)) {
168                 return;
169             }
170         }
171 
172         if (FontUtilities.isWindows || fontConfigFailed) {
173             return;
174         }
175 
176         long t0 = 0;
177         if (FontUtilities.isLogging()) {
178             t0 = System.nanoTime();
179         }
180 
181         FcCompFont[] fontArr = new FcCompFont[fontConfigNames.length];
182 
183         for (int i = 0; i< fontArr.length; i++) {
184             fontArr[i] = new FcCompFont();
185             fontArr[i].fcName = fontConfigNames[i];
186             int colonPos = fontArr[i].fcName.indexOf(':');
187             fontArr[i].fcFamily = fontArr[i].fcName.substring(0, colonPos);
188             fontArr[i].jdkName = FontUtilities.mapFcName(fontArr[i].fcFamily);
189             fontArr[i].style = i % 4; // depends on array order.
190         }
191         getFontConfig(getFCLocaleStr(), fcInfo, fontArr, includeFallbacks);
192         FontConfigFont anyFont = null;
193         /* If don't find anything (eg no libfontconfig), then just return */
194         for (int i = 0; i< fontArr.length; i++) {
195             FcCompFont fci = fontArr[i];
196             if (fci.firstFont == null) {
197                 if (FontUtilities.isLogging()) {
198                     FontUtilities.logInfo("Fontconfig returned no font for " + fontArr[i].fcName);
199                 }
200                 fontConfigFailed = true;
201             } else if (anyFont == null) {
202                 anyFont = fci.firstFont;
203             }
204         }
205 
206         if (anyFont == null) {
207             if (FontUtilities.isLogging()) {
208                 FontUtilities.logInfo("Fontconfig returned no fonts at all.");
209             }
210             fontConfigFailed = true;
211             return;
212         } else if (fontConfigFailed) {
213             for (int i = 0; i< fontArr.length; i++) {
214                 if (fontArr[i].firstFont == null) {
215                     fontArr[i].firstFont = anyFont;
216                 }
217             }
218         }
219 
220         fontConfigFonts = fontArr;
221 
222         if (FontUtilities.isLogging()) {
223             long t1 = System.nanoTime();
224             FontUtilities.logInfo("Time spent accessing fontconfig="
225                         + ((t1 - t0) / 1000000) + "ms.");
226 
227             for (int i = 0; i< fontConfigFonts.length; i++) {
228                 FcCompFont fci = fontConfigFonts[i];
229                 FontUtilities.logInfo("FC font " + fci.fcName+" maps to family " +
230                             fci.firstFont.familyName +
231                             " in file " + fci.firstFont.fontFile);
232                 if (fci.allFonts != null) {
233                     for (int f=0;f<fci.allFonts.length;f++) {
234                         FontConfigFont fcf = fci.allFonts[f];
235                         FontUtilities.logInfo("Family=" + fcf.familyName +
236                                     " Style="+ fcf.styleStr +
237                                     " Fullname="+fcf.fullName +
238                                     " File="+fcf.fontFile);
239                     }
240                 }
241             }
242         }
243     }
244 
registerFromFcInfo(FcCompFont fcInfo)245     public PhysicalFont registerFromFcInfo(FcCompFont fcInfo) {
246 
247         SunFontManager fm = SunFontManager.getInstance();
248 
249         /* If it's a TTC file we need to know that as we will need to
250          * make sure we return the right font */
251         String fontFile = fcInfo.firstFont.fontFile;
252         int offset = fontFile.length()-4;
253         if (offset <= 0) {
254             return null;
255         }
256         String ext = fontFile.substring(offset).toLowerCase();
257         boolean isTTC = ext.equals(".ttc");
258 
259         /* If this file is already registered, can just return its font.
260          * However we do need to check in case it's a TTC as we need
261          * a specific font, so rather than directly returning it, let
262          * findFont2D resolve that.
263          */
264         PhysicalFont physFont = fm.getRegisteredFontFile(fontFile);
265         if (physFont != null) {
266             if (isTTC) {
267                 Font2D f2d = fm.findFont2D(fcInfo.firstFont.familyName,
268                                            fcInfo.style,
269                                            FontManager.NO_FALLBACK);
270                 if (f2d instanceof PhysicalFont) { /* paranoia */
271                     return (PhysicalFont)f2d;
272                 } else {
273                     return null;
274                 }
275             } else {
276                 return physFont;
277             }
278         }
279 
280         /* If the font may hide a JRE font, we want to use the JRE version,
281          * so make it point to the JRE font.
282          */
283         physFont = fm.findJREDeferredFont(fcInfo.firstFont.familyName,
284                                           fcInfo.style);
285 
286         /* It is also possible the font file is on the "deferred" list,
287          * in which case we can just initialise it now.
288          */
289         if (physFont == null &&
290             fm.isDeferredFont(fontFile) == true) {
291             physFont = fm.initialiseDeferredFont(fcInfo.firstFont.fontFile);
292             /* use findFont2D to get the right font from TTC's */
293             if (physFont != null) {
294                 if (isTTC) {
295                     Font2D f2d = fm.findFont2D(fcInfo.firstFont.familyName,
296                                                fcInfo.style,
297                                                FontManager.NO_FALLBACK);
298                     if (f2d instanceof PhysicalFont) { /* paranoia */
299                         return (PhysicalFont)f2d;
300                     } else {
301                         return null;
302                     }
303                 } else {
304                     return physFont;
305                 }
306             }
307         }
308 
309         /* In the majority of cases we reach here, and need to determine
310          * the type and rank to register the font.
311          */
312         if (physFont == null) {
313             int fontFormat = SunFontManager.FONTFORMAT_NONE;
314             int fontRank = Font2D.UNKNOWN_RANK;
315 
316             if (ext.equals(".ttf") || isTTC) {
317                 fontFormat = SunFontManager.FONTFORMAT_TRUETYPE;
318                 fontRank = Font2D.TTF_RANK;
319             } else if (ext.equals(".pfa") || ext.equals(".pfb")) {
320                 fontFormat = SunFontManager.FONTFORMAT_TYPE1;
321                 fontRank = Font2D.TYPE1_RANK;
322             }
323             physFont = fm.registerFontFile(fcInfo.firstFont.fontFile, null,
324                                       fontFormat, true, fontRank);
325         }
326         return physFont;
327     }
328 
329     /*
330      * We need to return a Composite font which has as the font in
331      * its first slot one obtained from fontconfig.
332      */
getFontConfigFont(String name, int style)333     public CompositeFont getFontConfigFont(String name, int style) {
334 
335         name = name.toLowerCase();
336 
337         initFontConfigFonts(false);
338         if (fontConfigFonts == null) {
339             // This avoids an immediate NPE if fontconfig look up failed
340             // but doesn't guarantee this is a recoverable situation.
341             return null;
342         }
343 
344         FcCompFont fcInfo = null;
345         for (int i=0; i<fontConfigFonts.length; i++) {
346             if (name.equals(fontConfigFonts[i].fcFamily) &&
347                 style == fontConfigFonts[i].style) {
348                 fcInfo = fontConfigFonts[i];
349                 break;
350             }
351         }
352         if (fcInfo == null) {
353             fcInfo = fontConfigFonts[0];
354         }
355 
356         if (FontUtilities.isLogging()) {
357             FontUtilities.logInfo("FC name=" + name + " style=" + style +
358                                   " uses " + fcInfo.firstFont.familyName +
359                                   " in file: " + fcInfo.firstFont.fontFile);
360         }
361 
362         if (fcInfo.compFont != null) {
363             return fcInfo.compFont;
364         }
365 
366         /* jdkFont is going to be used for slots 1..N and as a fallback.
367          * Slot 0 will be the physical font from fontconfig.
368          */
369         FontManager fm = FontManagerFactory.getInstance();
370         CompositeFont jdkFont = (CompositeFont)
371             fm.findFont2D(fcInfo.jdkName, style, FontManager.LOGICAL_FALLBACK);
372 
373         if (fcInfo.firstFont.familyName == null ||
374             fcInfo.firstFont.fontFile == null) {
375             return (fcInfo.compFont = jdkFont);
376         }
377 
378         /* First, see if the family and exact style is already registered.
379          * If it is, use it. If it's not, then try to register it.
380          * If that registration fails (signalled by null) just return the
381          * regular JDK composite.
382          * Algorithmically styled fonts won't match on exact style, so
383          * will fall through this code, but the regisration code will
384          * find that file already registered and return its font.
385          */
386         FontFamily family = FontFamily.getFamily(fcInfo.firstFont.familyName);
387         PhysicalFont physFont = null;
388         if (family != null) {
389             Font2D f2D = family.getFontWithExactStyleMatch(fcInfo.style);
390             if (f2D instanceof PhysicalFont) {
391                 physFont = (PhysicalFont)f2D;
392             }
393         }
394 
395         if (physFont == null ||
396             !fcInfo.firstFont.fontFile.equals(physFont.platName)) {
397             physFont = registerFromFcInfo(fcInfo);
398             if (physFont == null) {
399                 return (fcInfo.compFont = jdkFont);
400             }
401             family = FontFamily.getFamily(physFont.getFamilyName(null));
402         }
403 
404         /* Now register the fonts in the family (the other styles) after
405          * checking that they aren't already registered and are actually in
406          * a different file. They may be the same file in CJK cases.
407          * For cases where they are different font files - eg as is common for
408          * Latin fonts, then we rely on fontconfig to report these correctly.
409          * Assume that all styles of this font are found by fontconfig,
410          * so we can find all the family members which must be registered
411          * together to prevent synthetic styling.
412          */
413         for (int i=0; i<fontConfigFonts.length; i++) {
414             FcCompFont fc = fontConfigFonts[i];
415             if (fc != fcInfo &&
416                 physFont.getFamilyName(null).equals(fc.firstFont.familyName) &&
417                 !fc.firstFont.fontFile.equals(physFont.platName) &&
418                 family.getFontWithExactStyleMatch(fc.style) == null) {
419 
420                 registerFromFcInfo(fontConfigFonts[i]);
421             }
422         }
423 
424         /* Now we have a physical font. We will back this up with the JDK
425          * logical font (sansserif, serif, or monospaced) that corresponds
426          * to the Pango/GTK/FC logical font name.
427          */
428         return (fcInfo.compFont = new CompositeFont(physFont, jdkFont));
429     }
430 
getFontConfigFonts()431     public FcCompFont[] getFontConfigFonts() {
432         return fontConfigFonts;
433     }
434 
435     /* Return an array of FcCompFont structs describing the primary
436      * font located for each of fontconfig/GTK/Pango's logical font names.
437      */
getFontConfig(String locale, FontConfigInfo fcInfo, FcCompFont[] fonts, boolean includeFallbacks)438     private static native void getFontConfig(String locale,
439                                              FontConfigInfo fcInfo,
440                                              FcCompFont[] fonts,
441                                              boolean includeFallbacks);
442 
populateFontConfig(FcCompFont[] fcInfo)443     void populateFontConfig(FcCompFont[] fcInfo) {
444         fontConfigFonts = fcInfo;
445     }
446 
loadFontConfig()447     FcCompFont[] loadFontConfig() {
448         initFontConfigFonts(true);
449         return fontConfigFonts;
450     }
451 
getFontConfigInfo()452     FontConfigInfo getFontConfigInfo() {
453         initFontConfigFonts(true);
454         return fcInfo;
455     }
456 
457     private static native int
getFontConfigAASettings(String locale, String fcFamily)458     getFontConfigAASettings(String locale, String fcFamily);
459 }
460