1 /*
2  * Copyright (c) 2008, 2013, 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.equals("")) {
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                     PlatformLogger logger = FontUtilities.getLogger();
199                     logger.info("Fontconfig returned no font for " +
200                                 fontArr[i].fcName);
201                 }
202                 fontConfigFailed = true;
203             } else if (anyFont == null) {
204                 anyFont = fci.firstFont;
205             }
206         }
207 
208         if (anyFont == null) {
209             if (FontUtilities.isLogging()) {
210                 PlatformLogger logger = FontUtilities.getLogger();
211                 logger.info("Fontconfig returned no fonts at all.");
212             }
213             fontConfigFailed = true;
214             return;
215         } else if (fontConfigFailed) {
216             for (int i = 0; i< fontArr.length; i++) {
217                 if (fontArr[i].firstFont == null) {
218                     fontArr[i].firstFont = anyFont;
219                 }
220             }
221         }
222 
223         fontConfigFonts = fontArr;
224 
225         if (FontUtilities.isLogging()) {
226 
227             PlatformLogger logger = FontUtilities.getLogger();
228 
229             long t1 = System.nanoTime();
230             logger.info("Time spent accessing fontconfig="
231                         + ((t1 - t0) / 1000000) + "ms.");
232 
233             for (int i = 0; i< fontConfigFonts.length; i++) {
234                 FcCompFont fci = fontConfigFonts[i];
235                 logger.info("FC font " + fci.fcName+" maps to family " +
236                             fci.firstFont.familyName +
237                             " in file " + fci.firstFont.fontFile);
238                 if (fci.allFonts != null) {
239                     for (int f=0;f<fci.allFonts.length;f++) {
240                         FontConfigFont fcf = fci.allFonts[f];
241                         logger.info("Family=" + fcf.familyName +
242                                     " Style="+ fcf.styleStr +
243                                     " Fullname="+fcf.fullName +
244                                     " File="+fcf.fontFile);
245                     }
246                 }
247             }
248         }
249     }
250 
registerFromFcInfo(FcCompFont fcInfo)251     public PhysicalFont registerFromFcInfo(FcCompFont fcInfo) {
252 
253         SunFontManager fm = SunFontManager.getInstance();
254 
255         /* If it's a TTC file we need to know that as we will need to
256          * make sure we return the right font */
257         String fontFile = fcInfo.firstFont.fontFile;
258         int offset = fontFile.length()-4;
259         if (offset <= 0) {
260             return null;
261         }
262         String ext = fontFile.substring(offset).toLowerCase();
263         boolean isTTC = ext.equals(".ttc");
264 
265         /* If this file is already registered, can just return its font.
266          * However we do need to check in case it's a TTC as we need
267          * a specific font, so rather than directly returning it, let
268          * findFont2D resolve that.
269          */
270         PhysicalFont physFont = fm.getRegisteredFontFile(fontFile);
271         if (physFont != null) {
272             if (isTTC) {
273                 Font2D f2d = fm.findFont2D(fcInfo.firstFont.familyName,
274                                            fcInfo.style,
275                                            FontManager.NO_FALLBACK);
276                 if (f2d instanceof PhysicalFont) { /* paranoia */
277                     return (PhysicalFont)f2d;
278                 } else {
279                     return null;
280                 }
281             } else {
282                 return physFont;
283             }
284         }
285 
286         /* If the font may hide a JRE font (eg fontconfig says it is
287          * Lucida Sans), we want to use the JRE version, so make it
288          * point to the JRE font.
289          */
290         physFont = fm.findJREDeferredFont(fcInfo.firstFont.familyName,
291                                           fcInfo.style);
292 
293         /* It is also possible the font file is on the "deferred" list,
294          * in which case we can just initialise it now.
295          */
296         if (physFont == null &&
297             fm.isDeferredFont(fontFile) == true) {
298             physFont = fm.initialiseDeferredFont(fcInfo.firstFont.fontFile);
299             /* use findFont2D to get the right font from TTC's */
300             if (physFont != null) {
301                 if (isTTC) {
302                     Font2D f2d = fm.findFont2D(fcInfo.firstFont.familyName,
303                                                fcInfo.style,
304                                                FontManager.NO_FALLBACK);
305                     if (f2d instanceof PhysicalFont) { /* paranoia */
306                         return (PhysicalFont)f2d;
307                     } else {
308                         return null;
309                     }
310                 } else {
311                     return physFont;
312                 }
313             }
314         }
315 
316         /* In the majority of cases we reach here, and need to determine
317          * the type and rank to register the font.
318          */
319         if (physFont == null) {
320             int fontFormat = SunFontManager.FONTFORMAT_NONE;
321             int fontRank = Font2D.UNKNOWN_RANK;
322 
323             if (ext.equals(".ttf") || isTTC) {
324                 fontFormat = SunFontManager.FONTFORMAT_TRUETYPE;
325                 fontRank = Font2D.TTF_RANK;
326             } else if (ext.equals(".pfa") || ext.equals(".pfb")) {
327                 fontFormat = SunFontManager.FONTFORMAT_TYPE1;
328                 fontRank = Font2D.TYPE1_RANK;
329             }
330             physFont = fm.registerFontFile(fcInfo.firstFont.fontFile, null,
331                                       fontFormat, true, fontRank);
332         }
333         return physFont;
334     }
335 
336     /*
337      * We need to return a Composite font which has as the font in
338      * its first slot one obtained from fontconfig.
339      */
getFontConfigFont(String name, int style)340     public CompositeFont getFontConfigFont(String name, int style) {
341 
342         name = name.toLowerCase();
343 
344         initFontConfigFonts(false);
345         if (fontConfigFonts == null) {
346             // This avoids an immediate NPE if fontconfig look up failed
347             // but doesn't guarantee this is a recoverable situation.
348             return null;
349         }
350 
351         FcCompFont fcInfo = null;
352         for (int i=0; i<fontConfigFonts.length; i++) {
353             if (name.equals(fontConfigFonts[i].fcFamily) &&
354                 style == fontConfigFonts[i].style) {
355                 fcInfo = fontConfigFonts[i];
356                 break;
357             }
358         }
359         if (fcInfo == null) {
360             fcInfo = fontConfigFonts[0];
361         }
362 
363         if (FontUtilities.isLogging()) {
364             FontUtilities.getLogger()
365                           .info("FC name=" + name + " style=" + style +
366                                 " uses " + fcInfo.firstFont.familyName +
367                                 " in file: " + fcInfo.firstFont.fontFile);
368         }
369 
370         if (fcInfo.compFont != null) {
371             return fcInfo.compFont;
372         }
373 
374         /* jdkFont is going to be used for slots 1..N and as a fallback.
375          * Slot 0 will be the physical font from fontconfig.
376          */
377         FontManager fm = FontManagerFactory.getInstance();
378         CompositeFont jdkFont = (CompositeFont)
379             fm.findFont2D(fcInfo.jdkName, style, FontManager.LOGICAL_FALLBACK);
380 
381         if (fcInfo.firstFont.familyName == null ||
382             fcInfo.firstFont.fontFile == null) {
383             return (fcInfo.compFont = jdkFont);
384         }
385 
386         /* First, see if the family and exact style is already registered.
387          * If it is, use it. If it's not, then try to register it.
388          * If that registration fails (signalled by null) just return the
389          * regular JDK composite.
390          * Algorithmically styled fonts won't match on exact style, so
391          * will fall through this code, but the regisration code will
392          * find that file already registered and return its font.
393          */
394         FontFamily family = FontFamily.getFamily(fcInfo.firstFont.familyName);
395         PhysicalFont physFont = null;
396         if (family != null) {
397             Font2D f2D = family.getFontWithExactStyleMatch(fcInfo.style);
398             if (f2D instanceof PhysicalFont) {
399                 physFont = (PhysicalFont)f2D;
400             }
401         }
402 
403         if (physFont == null ||
404             !fcInfo.firstFont.fontFile.equals(physFont.platName)) {
405             physFont = registerFromFcInfo(fcInfo);
406             if (physFont == null) {
407                 return (fcInfo.compFont = jdkFont);
408             }
409             family = FontFamily.getFamily(physFont.getFamilyName(null));
410         }
411 
412         /* Now register the fonts in the family (the other styles) after
413          * checking that they aren't already registered and are actually in
414          * a different file. They may be the same file in CJK cases.
415          * For cases where they are different font files - eg as is common for
416          * Latin fonts, then we rely on fontconfig to report these correctly.
417          * Assume that all styles of this font are found by fontconfig,
418          * so we can find all the family members which must be registered
419          * together to prevent synthetic styling.
420          */
421         for (int i=0; i<fontConfigFonts.length; i++) {
422             FcCompFont fc = fontConfigFonts[i];
423             if (fc != fcInfo &&
424                 physFont.getFamilyName(null).equals(fc.firstFont.familyName) &&
425                 !fc.firstFont.fontFile.equals(physFont.platName) &&
426                 family.getFontWithExactStyleMatch(fc.style) == null) {
427 
428                 registerFromFcInfo(fontConfigFonts[i]);
429             }
430         }
431 
432         /* Now we have a physical font. We will back this up with the JDK
433          * logical font (sansserif, serif, or monospaced) that corresponds
434          * to the Pango/GTK/FC logical font name.
435          */
436         return (fcInfo.compFont = new CompositeFont(physFont, jdkFont));
437     }
438 
439     /**
440      *
441      * @param locale
442      * @param fcFamily
443      * @return
444      */
getFontConfigFonts()445     public FcCompFont[] getFontConfigFonts() {
446         return fontConfigFonts;
447     }
448 
449     /* Return an array of FcCompFont structs describing the primary
450      * font located for each of fontconfig/GTK/Pango's logical font names.
451      */
getFontConfig(String locale, FontConfigInfo fcInfo, FcCompFont[] fonts, boolean includeFallbacks)452     private static native void getFontConfig(String locale,
453                                              FontConfigInfo fcInfo,
454                                              FcCompFont[] fonts,
455                                              boolean includeFallbacks);
456 
populateFontConfig(FcCompFont[] fcInfo)457     void populateFontConfig(FcCompFont[] fcInfo) {
458         fontConfigFonts = fcInfo;
459     }
460 
loadFontConfig()461     FcCompFont[] loadFontConfig() {
462         initFontConfigFonts(true);
463         return fontConfigFonts;
464     }
465 
getFontConfigInfo()466     FontConfigInfo getFontConfigInfo() {
467         initFontConfigFonts(true);
468         return fcInfo;
469     }
470 
471     private static native int
getFontConfigAASettings(String locale, String fcFamily)472     getFontConfigAASettings(String locale, String fcFamily);
473 }
474