1 /*
2  * Copyright (c) 1996, 2018, 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.peer.FontPeer;
29 import java.util.Locale;
30 import java.util.Vector;
31 import sun.font.SunFontManager;
32 import sun.java2d.FontSupport;
33 import java.nio.CharBuffer;
34 import java.nio.ByteBuffer;
35 
36 public abstract class PlatformFont implements FontPeer {
37 
38     static {
NativeLibLoader.loadLibraries()39         NativeLibLoader.loadLibraries();
initIDs()40         initIDs();
41     }
42 
43     protected FontDescriptor[] componentFonts;
44     protected char defaultChar;
45     protected FontConfiguration fontConfig;
46 
47     protected FontDescriptor defaultFont;
48 
49     protected String familyName;
50 
51     private Object[] fontCache;
52 
53     // Maybe this should be a property that is set based
54     // on the locale?
55     protected static int FONTCACHESIZE = 256;
56     protected static int FONTCACHEMASK = PlatformFont.FONTCACHESIZE - 1;
57     protected static String osVersion;
58 
PlatformFont(String name, int style)59     public PlatformFont(String name, int style){
60         SunFontManager sfm = SunFontManager.getInstance();
61         if (sfm instanceof FontSupport) {
62             fontConfig = ((FontSupport)sfm).getFontConfiguration();
63         }
64         if (fontConfig == null) {
65             return;
66         }
67 
68         // map given font name to a valid logical font family name
69         familyName = name.toLowerCase(Locale.ENGLISH);
70         if (!FontConfiguration.isLogicalFontFamilyName(familyName)) {
71             familyName = fontConfig.getFallbackFamilyName(familyName, "sansserif");
72         }
73 
74         componentFonts = fontConfig.getFontDescriptors(familyName, style);
75 
76         // search default character
77         //
78         char missingGlyphCharacter = getMissingGlyphCharacter();
79 
80         defaultChar = '?';
81         if (componentFonts.length > 0)
82             defaultFont = componentFonts[0];
83 
84         for (int i = 0; i < componentFonts.length; i++){
85             if (componentFonts[i].isExcluded(missingGlyphCharacter)) {
86                 continue;
87             }
88 
89             if (componentFonts[i].encoder.canEncode(missingGlyphCharacter)) {
90                 defaultFont = componentFonts[i];
91                 defaultChar = missingGlyphCharacter;
92                 break;
93             }
94         }
95     }
96 
97     /**
98      * Returns the character that should be rendered when a glyph
99      * is missing.
100      */
getMissingGlyphCharacter()101     protected abstract char getMissingGlyphCharacter();
102 
103     /**
104      * make a array of CharsetString with given String.
105      */
makeMultiCharsetString(String str)106     public CharsetString[] makeMultiCharsetString(String str){
107         return makeMultiCharsetString(str.toCharArray(), 0, str.length(), true);
108     }
109 
110     /**
111      * make a array of CharsetString with given String.
112      */
makeMultiCharsetString(String str, boolean allowdefault)113     public CharsetString[] makeMultiCharsetString(String str, boolean allowdefault){
114         return makeMultiCharsetString(str.toCharArray(), 0, str.length(), allowdefault);
115     }
116 
117     /**
118      * make a array of CharsetString with given char array.
119      * @param str The char array to convert.
120      * @param offset offset of first character of interest
121      * @param len number of characters to convert
122      */
makeMultiCharsetString(char[] str, int offset, int len)123     public CharsetString[] makeMultiCharsetString(char[] str, int offset, int len) {
124         return makeMultiCharsetString(str, offset, len, true);
125     }
126 
127     /**
128      * make a array of CharsetString with given char array.
129      * @param str The char array to convert.
130      * @param offset offset of first character of interest
131      * @param len number of characters to convert
132      * @param allowDefault whether to allow the default char.
133      * Setting this to true overloads the meaning of this method to
134      * return non-null only if all chars can be converted.
135      * @return array of CharsetString or if allowDefault is false and any
136      * of the returned chars would have been converted to a default char,
137      * then return null.
138      * This is used to choose alternative means of displaying the text.
139      */
makeMultiCharsetString(char[] str, int offset, int len, boolean allowDefault)140     public CharsetString[] makeMultiCharsetString(char[] str, int offset, int len,
141                                                   boolean allowDefault) {
142 
143         if (len < 1) {
144             return new CharsetString[0];
145         }
146         Vector<CharsetString> mcs = null;
147         char[] tmpStr = new char[len];
148         char tmpChar = defaultChar;
149         boolean encoded = false;
150 
151         FontDescriptor currentFont = defaultFont;
152 
153 
154         for (int i = 0; i < componentFonts.length; i++) {
155             if (componentFonts[i].isExcluded(str[offset])){
156                 continue;
157             }
158 
159             /* Need "encoded" variable to distinguish the case when
160              * the default char is the same as the encoded char.
161              * The defaultChar on Linux is '?' so it is needed there.
162              */
163             if (componentFonts[i].encoder.canEncode(str[offset])){
164                 currentFont = componentFonts[i];
165                 tmpChar = str[offset];
166                 encoded = true;
167                 break;
168             }
169         }
170         if (!allowDefault && !encoded) {
171             return null;
172         } else {
173             tmpStr[0] = tmpChar;
174         }
175 
176         int lastIndex = 0;
177         for (int i = 1; i < len; i++){
178             char ch = str[offset + i];
179             FontDescriptor fd = defaultFont;
180             tmpChar = defaultChar;
181             encoded = false;
182             for (int j = 0; j < componentFonts.length; j++){
183                 if (componentFonts[j].isExcluded(ch)){
184                     continue;
185                 }
186 
187                 if (componentFonts[j].encoder.canEncode(ch)){
188                     fd = componentFonts[j];
189                     tmpChar = ch;
190                     encoded = true;
191                     break;
192                 }
193             }
194             if (!allowDefault && !encoded) {
195                 return null;
196             } else {
197                 tmpStr[i] = tmpChar;
198             }
199             if (currentFont != fd){
200                 if (mcs == null) {
201                     mcs = new Vector<>(3);
202                 }
203                 mcs.addElement(new CharsetString(tmpStr, lastIndex,
204                                                  i-lastIndex, currentFont));
205                 currentFont = fd;
206                 fd = defaultFont;
207                 lastIndex = i;
208             }
209         }
210         CharsetString[] result;
211         CharsetString cs = new CharsetString(tmpStr, lastIndex,
212                                              len-lastIndex, currentFont);
213         if (mcs == null) {
214             result = new CharsetString[1];
215             result[0] = cs;
216         } else {
217             mcs.addElement(cs);
218             result = mcs.toArray(new CharsetString[mcs.size()]);
219         }
220         return result;
221     }
222 
223     /**
224      * Is it possible that this font's metrics require the multi-font calls?
225      * This might be true, for example, if the font supports kerning.
226     **/
mightHaveMultiFontMetrics()227     public boolean mightHaveMultiFontMetrics() {
228         return fontConfig != null;
229     }
230 
231     /**
232      * Specialized fast path string conversion for AWT.
233      */
makeConvertedMultiFontString(String str)234     public Object[] makeConvertedMultiFontString(String str)
235     {
236         return makeConvertedMultiFontChars(str.toCharArray(),0,str.length());
237     }
238 
makeConvertedMultiFontChars(char[] data, int start, int len)239     public Object[] makeConvertedMultiFontChars(char[] data,
240                                                 int start, int len)
241     {
242         Object[] result = new Object[2];
243         Object[] workingCache;
244         byte[] convertedData = null;
245         int stringIndex = start;
246         int convertedDataIndex = 0;
247         int resultIndex = 0;
248         int cacheIndex;
249         FontDescriptor currentFontDescriptor = null;
250         FontDescriptor lastFontDescriptor = null;
251         char currentDefaultChar;
252         PlatformFontCache theChar;
253 
254         // Simple bounds check
255         int end = start + len;
256         if (start < 0 || end > data.length) {
257             throw new ArrayIndexOutOfBoundsException();
258         }
259 
260         if(stringIndex >= end) {
261             return null;
262         }
263 
264         // coversion loop
265         while(stringIndex < end)
266         {
267             currentDefaultChar = data[stringIndex];
268 
269             // Note that cache sizes must be a power of two!
270             cacheIndex = (currentDefaultChar & PlatformFont.FONTCACHEMASK);
271 
272             theChar = (PlatformFontCache)getFontCache()[cacheIndex];
273 
274             // Is the unicode char we want cached?
275             if(theChar == null || theChar.uniChar != currentDefaultChar)
276             {
277                 /* find a converter that can convert the current character */
278                 currentFontDescriptor = defaultFont;
279                 currentDefaultChar = defaultChar;
280                 char ch = data[stringIndex];
281                 int componentCount = componentFonts.length;
282 
283                 for (int j = 0; j < componentCount; j++) {
284                     FontDescriptor fontDescriptor = componentFonts[j];
285 
286                     fontDescriptor.encoder.reset();
287                     //fontDescriptor.encoder.onUnmappleCharacterAction(...);
288 
289                     if (fontDescriptor.isExcluded(ch)) {
290                         continue;
291                     }
292                     if (fontDescriptor.encoder.canEncode(ch)) {
293                         currentFontDescriptor = fontDescriptor;
294                         currentDefaultChar = ch;
295                         break;
296                     }
297                 }
298                 try {
299                     char[] input = new char[1];
300                     input[0] = currentDefaultChar;
301 
302                     theChar = new PlatformFontCache();
303                     if (currentFontDescriptor.useUnicode()) {
304                         /*
305                         currentFontDescriptor.unicodeEncoder.encode(CharBuffer.wrap(input),
306                                                                     theChar.bb,
307                                                                     true);
308                         */
309                         if (FontDescriptor.isLE) {
310                             theChar.bb.put((byte)(input[0] & 0xff));
311                             theChar.bb.put((byte)(input[0] >>8));
312                         } else {
313                             theChar.bb.put((byte)(input[0] >> 8));
314                             theChar.bb.put((byte)(input[0] & 0xff));
315                         }
316                     }
317                     else  {
318                         currentFontDescriptor.encoder.encode(CharBuffer.wrap(input),
319                                                              theChar.bb,
320                                                              true);
321                     }
322                     theChar.fontDescriptor = currentFontDescriptor;
323                     theChar.uniChar = data[stringIndex];
324                     getFontCache()[cacheIndex] = theChar;
325                 } catch(Exception e){
326                     // Should never happen!
327                     System.err.println(e);
328                     e.printStackTrace();
329                     return null;
330                 }
331             }
332 
333             // Check to see if we've changed fonts.
334             if(lastFontDescriptor != theChar.fontDescriptor) {
335                 if(lastFontDescriptor != null) {
336                     result[resultIndex++] = lastFontDescriptor;
337                     result[resultIndex++] = convertedData;
338                     //  Add the size to the converted data field.
339                     if(convertedData != null) {
340                         convertedDataIndex -= 4;
341                         convertedData[0] = (byte)(convertedDataIndex >> 24);
342                         convertedData[1] = (byte)(convertedDataIndex >> 16);
343                         convertedData[2] = (byte)(convertedDataIndex >> 8);
344                         convertedData[3] = (byte)convertedDataIndex;
345                     }
346 
347                     if(resultIndex >= result.length) {
348                         Object[] newResult = new Object[result.length * 2];
349 
350                         System.arraycopy(result, 0, newResult, 0,
351                                          result.length);
352                         result = newResult;
353                     }
354                 }
355 
356                 if (theChar.fontDescriptor.useUnicode()) {
357                     convertedData = new byte[(end - stringIndex + 1) *
358                                         (int)theChar.fontDescriptor.unicodeEncoder.maxBytesPerChar()
359                                         + 4];
360                 }
361                 else  {
362                     convertedData = new byte[(end - stringIndex + 1) *
363                                         (int)theChar.fontDescriptor.encoder.maxBytesPerChar()
364                                         + 4];
365                 }
366 
367                 convertedDataIndex = 4;
368 
369                 lastFontDescriptor = theChar.fontDescriptor;
370             }
371 
372             byte[] ba = theChar.bb.array();
373             int size = theChar.bb.position();
374             if(size == 1) {
375                 convertedData[convertedDataIndex++] = ba[0];
376             }
377             else if(size == 2) {
378                 convertedData[convertedDataIndex++] = ba[0];
379                 convertedData[convertedDataIndex++] = ba[1];
380             } else if(size == 3) {
381                 convertedData[convertedDataIndex++] = ba[0];
382                 convertedData[convertedDataIndex++] = ba[1];
383                 convertedData[convertedDataIndex++] = ba[2];
384             } else if(size == 4) {
385                 convertedData[convertedDataIndex++] = ba[0];
386                 convertedData[convertedDataIndex++] = ba[1];
387                 convertedData[convertedDataIndex++] = ba[2];
388                 convertedData[convertedDataIndex++] = ba[3];
389             }
390             stringIndex++;
391         }
392 
393         result[resultIndex++] = lastFontDescriptor;
394         result[resultIndex] = convertedData;
395 
396         //  Add the size to the converted data field.
397         if(convertedData != null) {
398             convertedDataIndex -= 4;
399             convertedData[0] = (byte)(convertedDataIndex >> 24);
400             convertedData[1] = (byte)(convertedDataIndex >> 16);
401             convertedData[2] = (byte)(convertedDataIndex >> 8);
402             convertedData[3] = (byte)convertedDataIndex;
403         }
404         return result;
405     }
406 
407     /*
408      * Create fontCache on demand instead of during construction to
409      * reduce overall memory consumption.
410      *
411      * This method is declared final so that its code can be inlined
412      * by the compiler.
413      */
getFontCache()414     protected final Object[] getFontCache() {
415         // This method is not MT-safe by design. Since this is just a
416         // cache anyways, it's okay if we occasionally allocate the array
417         // twice or return an array which will be dereferenced and gced
418         // right away.
419         if (fontCache == null) {
420             fontCache = new Object[PlatformFont.FONTCACHESIZE];
421         }
422 
423         return fontCache;
424     }
425 
426     /**
427      * Initialize JNI field and method IDs
428      */
initIDs()429     private static native void initIDs();
430 
431     class PlatformFontCache
432     {
433         char uniChar;
434         FontDescriptor fontDescriptor;
435         ByteBuffer bb = ByteBuffer.allocate(4);
436     }
437 }
438