1 /*
2  * Copyright (c) 2009, 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.awt;
27 
28 import java.awt.GraphicsEnvironment;
29 import java.io.BufferedReader;
30 import java.io.File;
31 import java.io.FileReader;
32 import java.io.IOException;
33 import java.io.StreamTokenizer;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.Locale;
37 import java.util.Map;
38 import java.util.NoSuchElementException;
39 import java.util.StringTokenizer;
40 import java.util.Vector;
41 
42 import javax.swing.plaf.FontUIResource;
43 import sun.awt.motif.MFontConfiguration;
44 import sun.font.CompositeFont;
45 import sun.font.FontManager;
46 import sun.font.SunFontManager;
47 import sun.font.FontConfigManager;
48 import sun.font.FcFontConfiguration;
49 import sun.font.FontAccess;
50 import sun.font.FontUtilities;
51 import sun.font.NativeFont;
52 import sun.util.logging.PlatformLogger;
53 
54 /**
55  * The X11 implementation of {@link FontManager}.
56  */
57 public final class X11FontManager extends FcFontManager {
58 
59     // constants identifying XLFD and font ID fields
60     private static final int FOUNDRY_FIELD = 1;
61     private static final int FAMILY_NAME_FIELD = 2;
62     private static final int WEIGHT_NAME_FIELD = 3;
63     private static final int SLANT_FIELD = 4;
64     private static final int SETWIDTH_NAME_FIELD = 5;
65     private static final int ADD_STYLE_NAME_FIELD = 6;
66     private static final int PIXEL_SIZE_FIELD = 7;
67     private static final int POINT_SIZE_FIELD = 8;
68     private static final int RESOLUTION_X_FIELD = 9;
69     private static final int RESOLUTION_Y_FIELD = 10;
70     private static final int SPACING_FIELD = 11;
71     private static final int AVERAGE_WIDTH_FIELD = 12;
72     private static final int CHARSET_REGISTRY_FIELD = 13;
73     private static final int CHARSET_ENCODING_FIELD = 14;
74 
75     /*
76      * fontNameMap is a map from a fontID (which is a substring of an XLFD like
77      * "-monotype-arial-bold-r-normal-iso8859-7")
78      * to font file path like
79      * /usr/openwin/lib/locale/iso_8859_7/X11/fonts/TrueType/ArialBoldItalic.ttf
80      * It's used in a couple of methods like
81      * getFileNameFomPlatformName(..) to help locate the font file.
82      * We use this substring of a full XLFD because the font configuration files
83      * define the XLFDs in a way that's easier to make into a request.
84      * E.g., the -0-0-0-0-p-0- reported by X is -*-%d-*-*-p-*- in the font
85      * configuration files. We need to remove that part for comparisons.
86      */
87     private static Map fontNameMap = new HashMap();
88 
89     /*
90      * xlfdMap is a map from a platform path like
91      * /usr/openwin/lib/locale/ja/X11/fonts/TT/HG-GothicB.ttf to an XLFD like
92      * "-ricoh-hg gothic b-medium-r-normal--0-0-0-0-m-0-jisx0201.1976-0"
93      * Because there may be multiple native names, because the font is used
94      * to support multiple X encodings for example, the value of an entry in
95      * this map is always a vector where we store all the native names.
96      * For fonts which we don't understand the key isn't a pathname, its
97      * the full XLFD string like :-
98      * "-ricoh-hg gothic b-medium-r-normal--0-0-0-0-m-0-jisx0201.1976-0"
99      */
100     private static Map xlfdMap = new HashMap();
101 
102     /* xFontDirsMap is also a map from a font ID to a font filepath.
103      * The difference from fontNameMap is just that it does not have
104      * resolved symbolic links. Normally this is not interesting except
105      * that we need to know the directory in which a font was found to
106      * add it to the X font server path, since although the files may
107      * be linked, the fonts.dir is different and specific to the encoding
108      * handled by that directory. This map is nulled out after use to free
109      * heap space. If the optimal path is taken, such that all fonts in
110      * font configuration files are referenced by filename, then the font
111      * dir can be directly derived as its parent directory.
112      * If a font is used by two XLFDs, each corresponding to a different
113      * X11 font directory, then precautions must be taken to include both
114      * directories.
115      */
116      private static Map xFontDirsMap;
117 
118      /*
119       * This is the set of font directories needed to be on the X font path
120       * to enable AWT heavyweights to find all of the font configuration fonts.
121       * It is populated by :
122       * - awtfontpath entries in the fontconfig.properties
123       * - parent directories of "core" fonts used in the fontconfig.properties
124       * - looking up font dirs in the xFontDirsMap where the key is a fontID
125       *   (cut down version of the XLFD read from the font configuration file).
126       * This set is nulled out after use to free heap space.
127       */
128      private static HashSet<String> fontConfigDirs = null;
129 
130     /* These maps are used on Linux where we reference the Lucida oblique
131      * fonts in fontconfig files even though they aren't in the standard
132      * font directory. This explicitly remaps the XLFDs for these to the
133      * correct base font. This is needed to prevent composite fonts from
134      * defaulting to the Lucida Sans which is a bad substitute for the
135      * monospaced Lucida Sans Typewriter. Also these maps prevent the
136      * JRE from doing wasted work at start up.
137      */
138     HashMap<String, String> oblmap = null;
139 
140 
141     /*
142      * Used to eliminate redundant work. When a font directory is
143      * registered it added to this list. Subsequent registrations for the
144      * same directory can then be skipped by checking this Map.
145      * Access to this map is not synchronised here since creation
146      * of the singleton GE instance is already synchronised and that is
147      * the only code path that accesses this map.
148      */
149      private static HashMap registeredDirs = new HashMap();
150 
151      /* Array of directories to be added to the X11 font path.
152       * Used by static method called from Toolkits which use X11 fonts.
153       * Specifically this means MToolkit
154       */
155      private static String[] fontdirs = null;
156 
getInstance()157     public static X11FontManager getInstance() {
158         return (X11FontManager) SunFontManager.getInstance();
159     }
160 
161     /**
162      * Takes family name property in the following format:
163      * "-linotype-helvetica-medium-r-normal-sans-*-%d-*-*-p-*-iso8859-1"
164      * and returns the name of the corresponding physical font.
165      * This code is used to resolve font configuration fonts, and expects
166      * only to get called for these fonts.
167      */
168     @Override
getFileNameFromPlatformName(String platName)169     public String getFileNameFromPlatformName(String platName) {
170 
171         /* If the FontConfig file doesn't use xlfds, or its
172          * FcFontConfiguration, this may be already a file name.
173          */
174         if (platName.startsWith("/")) {
175             return platName;
176         }
177 
178         String fileName = null;
179         String fontID = specificFontIDForName(platName);
180 
181         /* If the font filename has been explicitly assigned in the
182          * font configuration file, use it. This avoids accessing
183          * the wrong fonts on Linux, where different fonts (some
184          * of which may not be usable by 2D) may share the same
185          * specific font ID. It may also speed up the lookup.
186          */
187         fileName = super.getFileNameFromPlatformName(platName);
188         if (fileName != null) {
189             if (isHeadless() && fileName.startsWith("-")) {
190                 /* if it's headless, no xlfd should be used */
191                     return null;
192             }
193             if (fileName.startsWith("/")) {
194                 /* If a path is assigned in the font configuration file,
195                  * it is required that the config file also specify using the
196                  * new awtfontpath key the X11 font directories
197                  * which must be added to the X11 font path to support
198                  * AWT access to that font. For that reason we no longer
199                  * have code here to add the parent directory to the list
200                  * of font config dirs, since the parent directory may not
201                  * be sufficient if fonts are symbolically linked to a
202                  * different directory.
203                  *
204                  * Add this XLFD (platform name) to the list of known
205                  * ones for this file.
206                  */
207                 Vector xVal = (Vector) xlfdMap.get(fileName);
208                 if (xVal == null) {
209                     /* Try to be robust on Linux distros which move fonts
210                      * around by verifying that the fileName represents a
211                      * file that exists.  If it doesn't, set it to null
212                      * to trigger a search.
213                      */
214                     if (getFontConfiguration().needToSearchForFile(fileName)) {
215                         fileName = null;
216                     }
217                     if (fileName != null) {
218                         xVal = new Vector();
219                         xVal.add(platName);
220                         xlfdMap.put(fileName, xVal);
221                     }
222                 } else {
223                     if (!xVal.contains(platName)) {
224                         xVal.add(platName);
225                     }
226                 }
227             }
228             if (fileName != null) {
229                 fontNameMap.put(fontID, fileName);
230                 return fileName;
231             }
232         }
233 
234         if (fontID != null) {
235             fileName = (String)fontNameMap.get(fontID);
236             /* On Linux check for the Lucida Oblique fonts */
237             if (fileName == null && (FontUtilities.isLinux || FontUtilities.isBSD) && !isOpenJDK()) {
238                 if (oblmap == null) {
239                     initObliqueLucidaFontMap();
240                 }
241                 String oblkey = getObliqueLucidaFontID(fontID);
242                 if (oblkey != null) {
243                     fileName = oblmap.get(oblkey);
244                 }
245             }
246             if (fontPath == null &&
247                 (fileName == null || !fileName.startsWith("/"))) {
248                 if (FontUtilities.debugFonts()) {
249                     FontUtilities.getLogger()
250                           .warning("** Registering all font paths because " +
251                                    "can't find file for " + platName);
252                 }
253                 fontPath = getPlatformFontPath(noType1Font);
254                 registerFontDirs(fontPath);
255                 if (FontUtilities.debugFonts()) {
256                     FontUtilities.getLogger()
257                             .warning("** Finished registering all font paths");
258                 }
259                 fileName = (String)fontNameMap.get(fontID);
260             }
261             if (fileName == null && !isHeadless()) {
262                 /* Query X11 directly to see if this font is available
263                  * as a native font.
264                  */
265                 fileName = getX11FontName(platName);
266             }
267             if (fileName == null) {
268                 fontID = switchFontIDForName(platName);
269                 fileName = (String)fontNameMap.get(fontID);
270             }
271             if (fileName != null) {
272                 fontNameMap.put(fontID, fileName);
273             }
274         }
275         return fileName;
276     }
277 
278     @Override
getNativeNames(String fontFileName, String platformName)279     protected String[] getNativeNames(String fontFileName,
280             String platformName) {
281         Vector nativeNames;
282         if ((nativeNames=(Vector)xlfdMap.get(fontFileName))==null) {
283             if (platformName == null) {
284                 return null;
285             } else {
286                 /* back-stop so that at least the name used in the
287                  * font configuration file is known as a native name
288                  */
289                 String []natNames = new String[1];
290                 natNames[0] = platformName;
291                 return natNames;
292             }
293         } else {
294             int len = nativeNames.size();
295             return (String[])nativeNames.toArray(new String[len]);
296         }
297     }
298 
299     /* NOTE: this method needs to be executed in a privileged context.
300      * The superclass constructor which is the primary caller of
301      * this method executes entirely in such a context. Additionally
302      * the loadFonts() method does too. So all should be well.
303 
304      */
305     @Override
registerFontDir(String path)306     protected void registerFontDir(String path) {
307         /* fonts.dir file format looks like :-
308          * 47
309          * Arial.ttf -monotype-arial-regular-r-normal--0-0-0-0-p-0-iso8859-1
310          * Arial-Bold.ttf -monotype-arial-bold-r-normal--0-0-0-0-p-0-iso8859-1
311          * ...
312          */
313         if (FontUtilities.debugFonts()) {
314             FontUtilities.getLogger().info("ParseFontDir " + path);
315         }
316         File fontsDotDir = new File(path + File.separator + "fonts.dir");
317         FileReader fr = null;
318         try {
319             if (fontsDotDir.canRead()) {
320                 fr = new FileReader(fontsDotDir);
321                 BufferedReader br = new BufferedReader(fr, 8192);
322                 StreamTokenizer st = new StreamTokenizer(br);
323                 st.eolIsSignificant(true);
324                 int ttype = st.nextToken();
325                 if (ttype == StreamTokenizer.TT_NUMBER) {
326                     int numEntries = (int)st.nval;
327                     ttype = st.nextToken();
328                     if (ttype == StreamTokenizer.TT_EOL) {
329                         st.resetSyntax();
330                         st.wordChars(32, 127);
331                         st.wordChars(128 + 32, 255);
332                         st.whitespaceChars(0, 31);
333 
334                         for (int i=0; i < numEntries; i++) {
335                             ttype = st.nextToken();
336                             if (ttype == StreamTokenizer.TT_EOF) {
337                                 break;
338                             }
339                             if (ttype != StreamTokenizer.TT_WORD) {
340                                 break;
341                             }
342                             int breakPos = st.sval.indexOf(' ');
343                             if (breakPos <= 0) {
344                                 /* On TurboLinux 8.0 a fonts.dir file had
345                                  * a line with integer value "24" which
346                                  * appeared to be the number of remaining
347                                  * entries in the file. This didn't add to
348                                  * the value on the first line of the file.
349                                  * Seemed like XFree86 didn't like this line
350                                  * much either. It failed to parse the file.
351                                  * Ignore lines like this completely, and
352                                  * don't let them count as an entry.
353                                  */
354                                 numEntries++;
355                                 ttype = st.nextToken();
356                                 if (ttype != StreamTokenizer.TT_EOL) {
357                                     break;
358                                 }
359 
360                                 continue;
361                             }
362                             if (st.sval.charAt(0) == '!') {
363                                 /* TurboLinux 8.0 comment line: ignore.
364                                  * can't use st.commentChar('!') to just
365                                  * skip because this line mustn't count
366                                  * against numEntries.
367                                  */
368                                 numEntries++;
369                                 ttype = st.nextToken();
370                                 if (ttype != StreamTokenizer.TT_EOL) {
371                                     break;
372                                 }
373                                 continue;
374                             }
375                             String fileName = st.sval.substring(0, breakPos);
376                             /* TurboLinux 8.0 uses some additional syntax to
377                              * indicate algorithmic styling values.
378                              * Ignore ':' separated files at the beginning
379                              * of the fileName
380                              */
381                             int lastColon = fileName.lastIndexOf(':');
382                             if (lastColon > 0) {
383                                 if (lastColon+1 >= fileName.length()) {
384                                     continue;
385                                 }
386                                 fileName = fileName.substring(lastColon+1);
387                             }
388                             String fontPart = st.sval.substring(breakPos+1);
389                             String fontID = specificFontIDForName(fontPart);
390                             String sVal = (String) fontNameMap.get(fontID);
391 
392                             if (FontUtilities.debugFonts()) {
393                                 PlatformLogger logger = FontUtilities.getLogger();
394                                 logger.info("file=" + fileName +
395                                             " xlfd=" + fontPart);
396                                 logger.info("fontID=" + fontID +
397                                             " sVal=" + sVal);
398                             }
399                             String fullPath = null;
400                             try {
401                                 File file = new File(path,fileName);
402                                 /* we may have a resolved symbolic link
403                                  * this becomes important for an xlfd we
404                                  * still need to know the location it was
405                                  * found to update the X server font path
406                                  * for use by AWT heavyweights - and when 2D
407                                  * wants to use the native rasteriser.
408                                  */
409                                 if (xFontDirsMap == null) {
410                                     xFontDirsMap = new HashMap();
411                                 }
412                                 xFontDirsMap.put(fontID, path);
413                                 fullPath = file.getCanonicalPath();
414                             } catch (IOException e) {
415                                 fullPath = path + File.separator + fileName;
416                             }
417                             Vector xVal = (Vector) xlfdMap.get(fullPath);
418                             if (FontUtilities.debugFonts()) {
419                                 FontUtilities.getLogger()
420                                       .info("fullPath=" + fullPath +
421                                             " xVal=" + xVal);
422                             }
423                             if ((xVal == null || !xVal.contains(fontPart)) &&
424                                 (sVal == null) || !sVal.startsWith("/")) {
425                                 if (FontUtilities.debugFonts()) {
426                                     FontUtilities.getLogger()
427                                           .info("Map fontID:"+fontID +
428                                                 "to file:" + fullPath);
429                                 }
430                                 fontNameMap.put(fontID, fullPath);
431                                 if (xVal == null) {
432                                     xVal = new Vector();
433                                     xlfdMap.put (fullPath, xVal);
434                                 }
435                                 xVal.add(fontPart);
436                             }
437 
438                             ttype = st.nextToken();
439                             if (ttype != StreamTokenizer.TT_EOL) {
440                                 break;
441                             }
442                         }
443                     }
444                 }
445                 fr.close();
446             }
447         } catch (IOException ioe1) {
448         } finally {
449             if (fr != null) {
450                 try {
451                     fr.close();
452                 }  catch (IOException ioe2) {
453                 }
454             }
455         }
456     }
457 
458     @Override
loadFonts()459     public void loadFonts() {
460         super.loadFonts();
461         /* These maps are greatly expanded during a loadFonts but
462          * can be reset to their initial state afterwards.
463          * Since preferLocaleFonts() and preferProportionalFonts() will
464          * trigger a partial repopulating from the FontConfiguration
465          * it has to be the inital (empty) state for the latter two, not
466          * simply nulling out.
467          * xFontDirsMap is a special case in that the implementation
468          * will typically not ever need to initialise it so it can be null.
469          */
470         xFontDirsMap = null;
471         xlfdMap = new HashMap(1);
472         fontNameMap = new HashMap(1);
473     }
474 
getObliqueLucidaFontID(String fontID)475     private String getObliqueLucidaFontID(String fontID) {
476         if (fontID.startsWith("-lucidasans-medium-i-normal") ||
477             fontID.startsWith("-lucidasans-bold-i-normal") ||
478             fontID.startsWith("-lucidatypewriter-medium-i-normal") ||
479             fontID.startsWith("-lucidatypewriter-bold-i-normal")) {
480             return fontID.substring(0, fontID.indexOf("-i-"));
481         } else {
482             return null;
483         }
484     }
485 
getX11FontName(String platName)486     private static String getX11FontName(String platName) {
487         String xlfd = platName.replaceAll("%d", "*");
488         if (NativeFont.fontExists(xlfd)) {
489             return xlfd;
490         } else {
491             return null;
492         }
493     }
494 
initObliqueLucidaFontMap()495     private void initObliqueLucidaFontMap() {
496         oblmap = new HashMap<String, String>();
497         oblmap.put("-lucidasans-medium",
498                    jreLibDirName+"/fonts/LucidaSansRegular.ttf");
499         oblmap.put("-lucidasans-bold",
500                    jreLibDirName+"/fonts/LucidaSansDemiBold.ttf");
501         oblmap.put("-lucidatypewriter-medium",
502                    jreLibDirName+"/fonts/LucidaTypewriterRegular.ttf");
503         oblmap.put("-lucidatypewriter-bold",
504                    jreLibDirName+"/fonts/LucidaTypewriterBold.ttf");
505     }
506 
isHeadless()507     private boolean isHeadless() {
508         GraphicsEnvironment ge =
509             GraphicsEnvironment.getLocalGraphicsEnvironment();
510         return GraphicsEnvironment.isHeadless();
511     }
512 
specificFontIDForName(String name)513     private String specificFontIDForName(String name) {
514 
515         int[] hPos = new int[14];
516         int hyphenCnt = 1;
517         int pos = 1;
518 
519         while (pos != -1 && hyphenCnt < 14) {
520             pos = name.indexOf('-', pos);
521             if (pos != -1) {
522                 hPos[hyphenCnt++] = pos;
523                     pos++;
524             }
525         }
526 
527         if (hyphenCnt != 14) {
528             if (FontUtilities.debugFonts()) {
529                 FontUtilities.getLogger()
530                     .severe("Font Configuration Font ID is malformed:" + name);
531             }
532             return name; // what else can we do?
533         }
534 
535         StringBuffer sb =
536             new StringBuffer(name.substring(hPos[FAMILY_NAME_FIELD-1],
537                                             hPos[SETWIDTH_NAME_FIELD]));
538         sb.append(name.substring(hPos[CHARSET_REGISTRY_FIELD-1]));
539         String retval = sb.toString().toLowerCase (Locale.ENGLISH);
540         return retval;
541     }
542 
switchFontIDForName(String name)543     private String switchFontIDForName(String name) {
544 
545         int[] hPos = new int[14];
546         int hyphenCnt = 1;
547         int pos = 1;
548 
549         while (pos != -1 && hyphenCnt < 14) {
550             pos = name.indexOf('-', pos);
551             if (pos != -1) {
552                 hPos[hyphenCnt++] = pos;
553                     pos++;
554             }
555         }
556 
557         if (hyphenCnt != 14) {
558             if (FontUtilities.debugFonts()) {
559                 FontUtilities.getLogger()
560                     .severe("Font Configuration Font ID is malformed:" + name);
561             }
562             return name; // what else can we do?
563         }
564 
565         String slant = name.substring(hPos[SLANT_FIELD-1]+1,
566                                            hPos[SLANT_FIELD]);
567         String family = name.substring(hPos[FAMILY_NAME_FIELD-1]+1,
568                                            hPos[FAMILY_NAME_FIELD]);
569         String registry = name.substring(hPos[CHARSET_REGISTRY_FIELD-1]+1,
570                                            hPos[CHARSET_REGISTRY_FIELD]);
571         String encoding = name.substring(hPos[CHARSET_ENCODING_FIELD-1]+1);
572 
573         if (slant.equals("i")) {
574             slant = "o";
575         } else if (slant.equals("o")) {
576             slant = "i";
577         }
578         // workaround for #4471000
579         if (family.equals("itc zapfdingbats")
580             && registry.equals("sun")
581             && encoding.equals("fontspecific")){
582             registry = "adobe";
583         }
584         StringBuffer sb =
585             new StringBuffer(name.substring(hPos[FAMILY_NAME_FIELD-1],
586                                             hPos[SLANT_FIELD-1]+1));
587         sb.append(slant);
588         sb.append(name.substring(hPos[SLANT_FIELD],
589                                  hPos[SETWIDTH_NAME_FIELD]+1));
590         sb.append(registry);
591         sb.append(name.substring(hPos[CHARSET_ENCODING_FIELD-1]));
592         String retval = sb.toString().toLowerCase (Locale.ENGLISH);
593         return retval;
594     }
595 
596     /**
597      * Returns the face name for the given XLFD.
598      */
getFileNameFromXLFD(String name)599     public String getFileNameFromXLFD(String name) {
600         String fileName = null;
601         String fontID = specificFontIDForName(name);
602         if (fontID != null) {
603             fileName = (String)fontNameMap.get(fontID);
604             if (fileName == null) {
605                 fontID = switchFontIDForName(name);
606                 fileName = (String)fontNameMap.get(fontID);
607             }
608             if (fileName == null) {
609                 fileName = getDefaultFontFile();
610             }
611         }
612         return fileName;
613     }
614 
615     /* Register just the paths, (it doesn't register the fonts).
616      * If a font configuration file has specified a baseFontPath
617      * fontPath is just those directories, unless on usage we
618      * find it doesn't contain what we need for the logical fonts.
619      * Otherwise, we register all the paths on Solaris, because
620      * the fontPath we have here is the complete one from
621      * parsing /var/sadm/install/contents, not just
622      * what's on the X font path (may be this should be
623      * changed).
624      * But for now what it means is that if we didn't do
625      * this then if the font weren't listed anywhere on the
626      * less complete font path we'd trigger loadFonts which
627      * actually registers the fonts. This may actually be
628      * the right thing tho' since that would also set up
629      * the X font path without which we wouldn't be able to
630      * display some "native" fonts.
631      * So something to revisit is that probably fontPath
632      * here ought to be only the X font path + jre font dir.
633      * loadFonts should have a separate native call to
634      * get the rest of the platform font path.
635      *
636      * Registering the directories can now be avoided in the
637      * font configuration initialisation when filename entries
638      * exist in the font configuration file for all fonts.
639      * (Perhaps a little confusingly a filename entry is
640      * actually keyed using the XLFD used in the font entries,
641      * and it maps *to* a real filename).
642      * In the event any are missing, registration of all
643      * directories will be invoked to find the real files.
644      *
645      * But registering the directory performed other
646      * functions such as filling in the map of all native names
647      * for the font. So when this method isn't invoked, they still
648      * must be found. This is mitigated by getNativeNames now
649      * being able to return at least the platform name, but mostly
650      * by ensuring that when a filename key is found, that
651      * xlfd key is stored as one of the set of platform names
652      * for the font. Its a set because typical font configuration
653      * files reference the same CJK font files using multiple
654      * X11 encodings. For the code that adds this to the map
655      * see X11GE.getFileNameFromPlatformName(..)
656      * If you don't get all of these then some code points may
657      * not use the Xserver, and will not get the PCF bitmaps
658      * that are available for some point sizes.
659      * So, in the event that there is such a problem,
660      * unconditionally making this call may be necessary, at
661      * some cost to JRE start-up
662      */
663     @Override
registerFontDirs(String pathName)664     protected void registerFontDirs(String pathName) {
665 
666         StringTokenizer parser = new StringTokenizer(pathName,
667                                                      File.pathSeparator);
668         try {
669             while (parser.hasMoreTokens()) {
670                 String dirPath = parser.nextToken();
671                 if (dirPath != null && !registeredDirs.containsKey(dirPath)) {
672                     registeredDirs.put(dirPath, null);
673                     registerFontDir(dirPath);
674                 }
675             }
676         } catch (NoSuchElementException e) {
677         }
678     }
679 
680     // An X font spec (xlfd) includes an encoding. The same TrueType font file
681     // may be referenced from different X font directories in font.dir files
682     // to support use in multiple encodings by X apps.
683     // So for the purposes of font configuration logical fonts where AWT
684     // heavyweights need to access the font via X APIs we need to ensure that
685     // the directory for precisely the encodings needed by this are added to
686     // the x font path. This requires that we note the platform names
687     // specified in font configuration files and use that to identify the
688     // X font directory that contains a font.dir file for that platform name
689     // and add it to the X font path (if display is local)
690     // Here we make use of an already built map of xlfds to font locations
691     // to add the font location to the set of those required to build the
692     // x font path needed by AWT.
693     // These are added to the x font path later.
694     // All this is necessary because on Solaris the font.dir directories
695     // may contain not real font files, but symbolic links to the actual
696     // location but that location is not suitable for the x font path, since
697     // it probably doesn't have a font.dir at all and certainly not one
698     // with the required encodings
699     // If the fontconfiguration file is properly set up so that all fonts
700     // are mapped to files then we will never trigger initialising
701     // xFontDirsMap (it will be null). In this case the awtfontpath entries
702     // must specify all the X11 directories needed by AWT.
703     @Override
addFontToPlatformFontPath(String platformName)704     protected void addFontToPlatformFontPath(String platformName) {
705         // Lazily initialize fontConfigDirs.
706         getPlatformFontPathFromFontConfig();
707         if (xFontDirsMap != null) {
708             String fontID = specificFontIDForName(platformName);
709             String dirName = (String)xFontDirsMap.get(fontID);
710             if (dirName != null) {
711                 fontConfigDirs.add(dirName);
712             }
713         }
714         return;
715     }
716 
getPlatformFontPathFromFontConfig()717     private void getPlatformFontPathFromFontConfig() {
718         if (fontConfigDirs == null) {
719             fontConfigDirs = getFontConfiguration().getAWTFontPathSet();
720             if (FontUtilities.debugFonts() && fontConfigDirs != null) {
721                 String[] names = fontConfigDirs.toArray(new String[0]);
722                 for (int i=0;i<names.length;i++) {
723                     FontUtilities.getLogger().info("awtfontpath : " + names[i]);
724                 }
725             }
726         }
727     }
728 
729     @Override
registerPlatformFontsUsedByFontConfiguration()730     protected void registerPlatformFontsUsedByFontConfiguration() {
731         // Lazily initialize fontConfigDirs.
732         getPlatformFontPathFromFontConfig();
733         if (fontConfigDirs == null) {
734             return;
735         }
736         if (FontUtilities.isLinux || FontUtilities.isBSD) {
737             fontConfigDirs.add(jreLibDirName+File.separator+"oblique-fonts");
738         }
739         fontdirs = (String[])fontConfigDirs.toArray(new String[0]);
740     }
741 
742     // Implements SunGraphicsEnvironment.createFontConfiguration.
createFontConfiguration()743     protected FontConfiguration createFontConfiguration() {
744         /* The logic here decides whether to use a preconfigured
745          * fontconfig.properties file, or synthesise one using platform APIs.
746          * On Solaris (as opposed to OpenSolaris) we try to use the
747          * pre-configured ones, but if the files it specifies are missing
748          * we fail-safe to synthesising one. This might happen if Solaris
749          * changes its fonts.
750          * For OpenSolaris I don't expect us to ever create fontconfig files,
751          * so it will always synthesise. Note that if we misidentify
752          * OpenSolaris as Solaris, then the test for the presence of
753          * Solaris-only font files will correct this.
754          * For Linux we require an exact match of distro and version to
755          * use the preconfigured file, and also that it points to
756          * existent fonts.
757          * If synthesising fails, we fall back to any preconfigured file
758          * and do the best we can. For the commercial JDK this will be
759          * fine as it includes the Lucida fonts. OpenJDK should not hit
760          * this as the synthesis should always work on its platforms.
761          */
762         FontConfiguration mFontConfig = new MFontConfiguration(this);
763         if (FontUtilities.isOpenSolaris ||
764             ((FontUtilities.isLinux || FontUtilities.isBSD) &&
765              (!mFontConfig.foundOsSpecificFile() ||
766               !mFontConfig.fontFilesArePresent()) ||
767              (FontUtilities.isSolaris && !mFontConfig.fontFilesArePresent()))) {
768             FcFontConfiguration fcFontConfig =
769                 new FcFontConfiguration(this);
770             if (fcFontConfig.init()) {
771                 return fcFontConfig;
772             }
773         }
774         mFontConfig.init();
775         return mFontConfig;
776     }
777     public FontConfiguration
createFontConfiguration(boolean preferLocaleFonts, boolean preferPropFonts)778         createFontConfiguration(boolean preferLocaleFonts,
779                                 boolean preferPropFonts) {
780 
781         return new MFontConfiguration(this,
782                                       preferLocaleFonts, preferPropFonts);
783     }
784 
getFontPath(boolean noType1Fonts)785     protected synchronized String getFontPath(boolean noType1Fonts) {
786         isHeadless(); // make sure GE is inited, as its the X11 lock.
787         return getFontPathNative(noType1Fonts, true);
788     }
789 
790     @Override
getFontConfigFUIR(String family, int style, int size)791     protected FontUIResource getFontConfigFUIR(String family, int style, int size) {
792 
793         CompositeFont font2D = getFontConfigManager().getFontConfigFont(family, style);
794 
795         if (font2D == null) { // Not expected, just a precaution.
796            return new FontUIResource(family, style, size);
797         }
798 
799         /* The name of the font will be that of the physical font in slot,
800          * but by setting the handle to that of the CompositeFont it
801          * renders as that CompositeFont.
802          * It also needs to be marked as a created font which is the
803          * current mechanism to signal that deriveFont etc must copy
804          * the handle from the original font.
805          */
806         FontUIResource fuir =
807             new FontUIResource(font2D.getFamilyName(null), style, size);
808         FontAccess.getFontAccess().setFont2D(fuir, font2D.handle);
809         FontAccess.getFontAccess().setCreatedFont(fuir);
810         return fuir;
811     }
812 }
813