1 /*
2  * Copyright (c) 2003, 2006, 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.awt.Font;
29 import java.awt.FontFormatException;
30 import java.awt.GraphicsEnvironment;
31 import java.awt.font.FontRenderContext;
32 import java.awt.geom.GeneralPath;
33 import java.awt.geom.Point2D;
34 import java.awt.geom.Rectangle2D;
35 import java.io.UnsupportedEncodingException;
36 import java.lang.ref.WeakReference;
37 import java.util.Locale;
38 
39 /*
40  * Ideally there would be no native fonts used, and this class would be
41  * unneeded and removed. Presently it is still needed until such time
42  * as font configuration files (or the implementation equivalent) can have
43  * all references to fonts that are not handled via Java 2D removed.
44  * Currently there are two cases where this class is needed, both on
45  * Unix, primarily Solaris, but useful on Linux too if fonts have moved.
46  * 1. Some legacy F3 fonts are still referenced so that AWT "X/Motif"
47  * can get dingbats and symbols from them. This can be dispensed with when
48  * either AWT is based on 2D, or when the X font path is known to always
49  * contain a Type1 or TrueType font that can be used in font configuration
50  * files to replace the F3 fonts.
51  * 2. When location of font files by 2D fails, because of some system
52  * configuration problem, it is desirable to have a fall back to some
53  * functionality that lessens the immediate impact on users. Being able
54  * to perform limited operations by using bitmaps from X11 helps here.
55  */
56 
57 public class NativeFont extends PhysicalFont {
58 
59     String encoding;
60 
61     private int numGlyphs = -1;
62     boolean isBitmapDelegate;
63     PhysicalFont delegateFont;
64 
65     /**
66      * Verifies native font is accessible.
67      * @throws FontFormatException - if the font can't be located.
68      */
NativeFont(String platName, boolean bitmapDelegate)69     public NativeFont(String platName, boolean bitmapDelegate)
70         throws FontFormatException {
71         super(platName, null);
72 
73         /* This is set true if this is an instance of a NativeFont
74          * created by some other font, to get native bitmaps.
75          * The delegating font will call this font only for "basic"
76          * cases - ie non-rotated, uniform scale, monochrome bitmaps.
77          * If this is false, then this instance may need to itself
78          * delegate to another font for non-basic cases. Since
79          * NativeFonts are used in that way only for symbol and dingbats
80          * we know its safe to delegate these to the JRE's default
81          * physical font (Lucida Sans Regular).
82          */
83         isBitmapDelegate = bitmapDelegate;
84 
85         if (GraphicsEnvironment.isHeadless()) {
86             throw new FontFormatException("Native font in headless toolkit");
87         }
88         fontRank = Font2D.NATIVE_RANK;
89         initNames();
90         if (getNumGlyphs() == 0) {
91           throw new FontFormatException("Couldn't locate font" + platName);
92         }
93     }
94 
initNames()95     private void initNames() throws FontFormatException {
96         /* Valid XLFD has exactly 14 "-" chars.
97          * First run over the string to verify have at least this many
98          * At the same time record the locations of the hyphens
99          * so we can just pick the right substring later on
100          */
101         int[] hPos = new int[14];
102         int hyphenCnt = 1;
103         int pos = 1;
104 
105         String xlfd = platName.toLowerCase(Locale.ENGLISH);
106         if (xlfd.startsWith("-")) {
107             while (pos != -1 && hyphenCnt < 14) {
108                 pos = xlfd.indexOf('-', pos);
109                 if (pos != -1) {
110                     hPos[hyphenCnt++] = pos;
111                     pos++;
112                 }
113             }
114         }
115 
116         if (hyphenCnt == 14 && pos != -1) {
117 
118             /* Capitalise words in the Family name */
119             String tmpFamily = xlfd.substring(hPos[1]+1, hPos[2]);
120             StringBuilder sBuffer = new StringBuilder(tmpFamily);
121             char ch = Character.toUpperCase(sBuffer.charAt(0));
122             sBuffer.replace(0, 1, String.valueOf(ch));
123             for (int i=1;i<sBuffer.length()-1; i++) {
124                 if (sBuffer.charAt(i) == ' ') {
125                     ch = Character.toUpperCase(sBuffer.charAt(i+1));
126                     sBuffer.replace(i+1, i+2, String.valueOf(ch));
127                 }
128             }
129             familyName = sBuffer.toString();
130 
131             String tmpWeight = xlfd.substring(hPos[2]+1, hPos[3]);
132             String tmpSlant = xlfd.substring(hPos[3]+1, hPos[4]);
133 
134             String styleStr = null;
135 
136             if (tmpWeight.indexOf("bold") >= 0 ||
137                 tmpWeight.indexOf("demi") >= 0) {
138                 style |= Font.BOLD;
139                 styleStr = "Bold";
140             }
141 
142             if (tmpSlant.equals("i") ||
143                 tmpSlant.indexOf("italic") >= 0) {
144                 style |= Font.ITALIC;
145 
146                 if (styleStr == null) {
147                     styleStr = "Italic";
148                 } else {
149                     styleStr = styleStr + " Italic";
150                 }
151             }
152             else if (tmpSlant.equals("o") ||
153                 tmpSlant.indexOf("oblique") >= 0) {
154                 style |= Font.ITALIC;
155                 if (styleStr == null) {
156                     styleStr = "Oblique";
157                 } else {
158                     styleStr = styleStr + " Oblique";
159                 }
160             }
161 
162             if (styleStr == null) {
163                 fullName = familyName;
164             } else {
165                 fullName = familyName + " " + styleStr;
166             }
167 
168             encoding = xlfd.substring(hPos[12]+1);
169             if (encoding.startsWith("-")) {
170                 encoding = xlfd.substring(hPos[13]+1);
171             }
172             if (encoding.indexOf("fontspecific") >= 0) {
173                 if (tmpFamily.indexOf("dingbats") >= 0) {
174                     encoding = "dingbats";
175                 } else if (tmpFamily.indexOf("symbol") >= 0) {
176                     encoding = "symbol";
177                 } else {
178                     encoding = "iso8859-1";
179                 }
180             }
181         } else {
182             throw new FontFormatException("Bad native name " + platName);
183 //             familyName = "Unknown";
184 //             fullName = "Unknown";
185 //             style = Font.PLAIN;
186 //             encoding = "iso8859-1";
187         }
188     }
189 
190     /* Wildcard all the size fields in the XLFD and retrieve a list of
191      * XLFD's that match.
192      * We only look for scaleable fonts, so we can just replace the 0's
193      * with *'s and see what we get back
194      * No matches means even the scaleable version wasn't found. This is
195      * means the X font path isn't set up for this font at all.
196      * One match means only the scaleable version we started with was found
197      * -monotype-arial-bold-i-normal--0-0-0-0-p-0-iso8859-1
198      * Two matches apparently means as well as the above, a scaleable
199      * specified for 72 dpi is found, not that there are bitmaps : eg
200      * -monotype-arial-bold-i-normal--0-0-72-72-p-0-iso8859-1
201      * So require at least 3 matches (no need to parse) to determine that
202      * there are external bitmaps.
203      */
hasExternalBitmaps(String platName)204     static boolean hasExternalBitmaps(String platName) {
205         /* Turn -monotype-arial-bold-i-normal--0-0-0-0-p-0-iso8859-1
206          * into -monotype-arial-bold-i-normal--*-*-*-*-p-*-iso8859-1
207          * by replacing all -0- substrings with -*-
208          */
209         StringBuilder sb = new StringBuilder(platName);
210         int pos = sb.indexOf("-0-");
211         while (pos >=0) {
212             sb.replace(pos+1, pos+2, "*");
213             pos = sb.indexOf("-0-", pos);
214         };
215         String xlfd = sb.toString();
216         byte[] bytes = null;
217         try {
218             bytes = xlfd.getBytes("UTF-8");
219         } catch (UnsupportedEncodingException e) {
220             bytes = xlfd.getBytes();
221         }
222         return haveBitmapFonts(bytes);
223     }
224 
fontExists(String xlfd)225     public static boolean fontExists(String xlfd) {
226         byte[] bytes = null;
227         try {
228             bytes = xlfd.getBytes("UTF-8");
229         } catch (UnsupportedEncodingException e) {
230             bytes = xlfd.getBytes();
231         }
232         return fontExists(bytes);
233     }
234 
haveBitmapFonts(byte[] xlfd)235     private static native boolean haveBitmapFonts(byte[] xlfd);
fontExists(byte[] xlfd)236     private static native boolean fontExists(byte[] xlfd);
237 
getMapper()238     public CharToGlyphMapper getMapper() {
239         if (mapper == null) {
240             if (isBitmapDelegate) {
241                 /* we are a delegate */
242                 mapper = new NativeGlyphMapper(this);
243             } else {
244                 /* we need to delegate */
245                 SunFontManager fm = SunFontManager.getInstance();
246                 delegateFont = fm.getDefaultPhysicalFont();
247                 mapper = delegateFont.getMapper();
248             }
249         }
250         return mapper;
251     }
252 
createStrike(FontStrikeDesc desc)253     FontStrike createStrike(FontStrikeDesc desc) {
254         if (isBitmapDelegate) {
255             return new NativeStrike(this, desc);
256         } else {
257             if (delegateFont == null) {
258                 SunFontManager fm = SunFontManager.getInstance();
259                 delegateFont = fm.getDefaultPhysicalFont();
260             }
261             /* If no FileFont's are found, delegate font may be
262              * a NativeFont, so we need to avoid recursing here.
263              */
264             if (delegateFont instanceof NativeFont) {
265                 return new NativeStrike((NativeFont)delegateFont, desc);
266             }
267             FontStrike delegate = delegateFont.createStrike(desc);
268             return new DelegateStrike(this, desc, delegate);
269         }
270     }
271 
getMaxCharBounds(FontRenderContext frc)272     public Rectangle2D getMaxCharBounds(FontRenderContext frc) {
273             return null;
274     }
275 
getFontMetrics(long pScalerContext)276     native StrikeMetrics getFontMetrics(long pScalerContext);
277 
getGlyphAdvance(long pContext, int glyphCode)278     native float getGlyphAdvance(long pContext, int glyphCode);
279 
getGlyphOutlineBounds(long pScalerContext, int glyphCode)280     Rectangle2D.Float getGlyphOutlineBounds(long pScalerContext,
281                                             int glyphCode) {
282         return new Rectangle2D.Float(0f, 0f, 0f, 0f);
283     }
284 
getGlyphOutline(long pScalerContext, int glyphCode, float x, float y)285     public GeneralPath getGlyphOutline(long pScalerContext,
286                                        int glyphCode,
287                                        float x,
288                                        float y) {
289         return null;
290     }
291 
getGlyphImage(long pScalerContext, int glyphCode)292     native long getGlyphImage(long pScalerContext, int glyphCode);
293 
getGlyphImageNoDefault(long pScalerContext, int glyphCode)294     native long getGlyphImageNoDefault(long pScalerContext, int glyphCode);
295 
getGlyphMetrics(long pScalerContext, int glyphCode, Point2D.Float metrics)296     void getGlyphMetrics(long pScalerContext, int glyphCode,
297                         Point2D.Float metrics) {
298         throw new RuntimeException("this should be called on the strike");
299     }
300 
getGlyphVectorOutline(long pScalerContext, int[] glyphs, int numGlyphs, float x, float y)301     public  GeneralPath getGlyphVectorOutline(long pScalerContext,
302                                               int[] glyphs, int numGlyphs,
303                                               float x,  float y) {
304         return null;
305     }
306 
countGlyphs(byte[] platformNameBytes, int ptSize)307     private native int countGlyphs(byte[] platformNameBytes, int ptSize);
308 
getNumGlyphs()309     public int getNumGlyphs() {
310         if (numGlyphs == -1) {
311             byte[] bytes = getPlatformNameBytes(8);
312             numGlyphs = countGlyphs(bytes, 8);
313         }
314         return numGlyphs;
315     }
316 
getDelegateFont()317     PhysicalFont getDelegateFont() {
318         if (delegateFont == null) {
319             SunFontManager fm = SunFontManager.getInstance();
320             delegateFont = fm.getDefaultPhysicalFont();
321         }
322         return delegateFont;
323     }
324 
325     /* Specify that the dpi is 72x72, as this corresponds to JDK's
326      * default user space. These are the 10th and 11th fields in the XLFD.
327      * ptSize in XLFD is in 10th's of a point so multiply by 10,
328      * Replace the 9th field in the XLFD (ie after the 8th hyphen)
329      * with this pt size (this corresponds to the field that's "%d" in the
330      * font configuration files). Wild card the other numeric fields.
331      * ie to request 12 pt Times New Roman italic font, use an XLFD like :
332      * -monotype-times new roman-regular-i---*-120-72-72-p-*-iso8859-1
333      */
getPlatformNameBytes(int ptSize)334     byte[] getPlatformNameBytes(int ptSize) {
335         int[] hPos = new int[14];
336         int hyphenCnt = 1;
337         int pos = 1;
338 
339         while (pos != -1 && hyphenCnt < 14) {
340             pos = platName.indexOf('-', pos);
341             if (pos != -1) {
342                 hPos[hyphenCnt++] = pos;
343                     pos++;
344             }
345         }
346         String sizeStr = Integer.toString((int)Math.abs(ptSize)*10);
347         StringBuilder sb = new StringBuilder(platName);
348         /* work backwards so as to not invalidate the positions. */
349         sb.replace(hPos[11]+1, hPos[12], "*");
350 
351         sb.replace(hPos[9]+1, hPos[10], "72");
352 
353         sb.replace(hPos[8]+1, hPos[9], "72");
354 
355         /* replace the 3 lines above with the next 3 lines to get the 1.4.2
356          * behaviour
357          */
358 //      sb.replace(hPos[11]+1, hPos[12], "0");
359 //      sb.replace(hPos[9]+1, hPos[10], "0");
360 //      sb.replace(hPos[8]+1, hPos[9], "0");
361 
362         sb.replace(hPos[7]+1, hPos[8], sizeStr);
363 
364         sb.replace(hPos[6]+1, hPos[7], "*");
365 
366         /* replace the 1 line above with the next line to get the 1.4.2
367          * behaviour
368          */
369 //      sb.replace(hPos[6]+1, hPos[7], "0");
370 
371         /* comment out this block to the the 1.4.2 behaviour */
372         if (hPos[0] == 0 && hPos[1] == 1) {
373             /* null foundry name : some linux font configuration files have
374              * symbol font entries like this and its just plain wrong.
375              * Replace with a wild card. (Although those fonts should be
376              * located via disk access rather than X11).
377              */
378            sb.replace(hPos[0]+1, hPos[1], "*");
379         }
380 
381         String xlfd = sb.toString();
382         byte[] bytes = null;
383         try {
384             bytes = xlfd.getBytes("UTF-8");
385         } catch (UnsupportedEncodingException e) {
386             bytes = xlfd.getBytes();
387         }
388         return bytes;
389     }
390 
toString()391     public String toString() {
392         return " ** Native Font: Family="+familyName+ " Name="+fullName+
393             " style="+style+" nativeName="+platName;
394     }
395 }
396