1 /*
   2  * Copyright (c) 1996, 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.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 {
  39         NativeLibLoader.loadLibraries();
  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 
  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      */
 101     protected abstract char getMissingGlyphCharacter();
 102 
 103     /**
 104      * make a array of CharsetString with given String.
 105      */
 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      */
 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      */
 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      */
 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 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 = new CharsetString[mcs.size()];
 219             for (int i = 0; i < mcs.size(); i++){
 220                 result[i] = (CharsetString)mcs.elementAt(i);
 221             }
 222         }
 223         return result;
 224     }
 225 
 226     /**
 227      * Is it possible that this font's metrics require the multi-font calls?
 228      * This might be true, for example, if the font supports kerning.
 229     **/
 230     public boolean mightHaveMultiFontMetrics() {
 231         return fontConfig != null;
 232     }
 233 
 234     /**
 235      * Specialized fast path string conversion for AWT.
 236      */
 237     public Object[] makeConvertedMultiFontString(String str)
 238     {
 239         return makeConvertedMultiFontChars(str.toCharArray(),0,str.length());
 240     }
 241 
 242     public Object[] makeConvertedMultiFontChars(char[] data,
 243                                                 int start, int len)
 244     {
 245         Object[] result = new Object[2];
 246         Object[] workingCache;
 247         byte[] convertedData = null;
 248         int stringIndex = start;
 249         int convertedDataIndex = 0;
 250         int resultIndex = 0;
 251         int cacheIndex;
 252         FontDescriptor currentFontDescriptor = null;
 253         FontDescriptor lastFontDescriptor = null;
 254         char currentDefaultChar;
 255         PlatformFontCache theChar;
 256 
 257         // Simple bounds check
 258         int end = start + len;
 259         if (start < 0 || end > data.length) {
 260             throw new ArrayIndexOutOfBoundsException();
 261         }
 262 
 263         if(stringIndex >= end) {
 264             return null;
 265         }
 266 
 267         // coversion loop
 268         while(stringIndex < end)
 269         {
 270             currentDefaultChar = data[stringIndex];
 271 
 272             // Note that cache sizes must be a power of two!
 273             cacheIndex = (currentDefaultChar & PlatformFont.FONTCACHEMASK);
 274 
 275             theChar = (PlatformFontCache)getFontCache()[cacheIndex];
 276 
 277             // Is the unicode char we want cached?
 278             if(theChar == null || theChar.uniChar != currentDefaultChar)
 279             {
 280                 /* find a converter that can convert the current character */
 281                 currentFontDescriptor = defaultFont;
 282                 currentDefaultChar = defaultChar;
 283                 char ch = data[stringIndex];
 284                 int componentCount = componentFonts.length;
 285 
 286                 for (int j = 0; j < componentCount; j++) {
 287                     FontDescriptor fontDescriptor = componentFonts[j];
 288 
 289                     fontDescriptor.encoder.reset();
 290                     //fontDescriptor.encoder.onUnmappleCharacterAction(...);
 291 
 292                     if (fontDescriptor.isExcluded(ch)) {
 293                         continue;
 294                     }
 295                     if (fontDescriptor.encoder.canEncode(ch)) {
 296                         currentFontDescriptor = fontDescriptor;
 297                         currentDefaultChar = ch;
 298                         break;
 299                     }
 300                 }
 301                 try {
 302                     char[] input = new char[1];
 303                     input[0] = currentDefaultChar;
 304 
 305                     theChar = new PlatformFontCache();
 306                     if (currentFontDescriptor.useUnicode()) {
 307                         /*
 308                         currentFontDescriptor.unicodeEncoder.encode(CharBuffer.wrap(input),
 309                                                                     theChar.bb,
 310                                                                     true);
 311                         */
 312                         if (FontDescriptor.isLE) {
 313                             theChar.bb.put((byte)(input[0] & 0xff));
 314                             theChar.bb.put((byte)(input[0] >>8));
 315                         } else {
 316                             theChar.bb.put((byte)(input[0] >> 8));
 317                             theChar.bb.put((byte)(input[0] & 0xff));
 318                         }
 319                     }
 320                     else  {
 321                         currentFontDescriptor.encoder.encode(CharBuffer.wrap(input),
 322                                                              theChar.bb,
 323                                                              true);
 324                     }
 325                     theChar.fontDescriptor = currentFontDescriptor;
 326                     theChar.uniChar = data[stringIndex];
 327                     getFontCache()[cacheIndex] = theChar;
 328                 } catch(Exception e){
 329                     // Should never happen!
 330                     System.err.println(e);
 331                     e.printStackTrace();
 332                     return null;
 333                 }
 334             }
 335 
 336             // Check to see if we've changed fonts.
 337             if(lastFontDescriptor != theChar.fontDescriptor) {
 338                 if(lastFontDescriptor != null) {
 339                     result[resultIndex++] = lastFontDescriptor;
 340                     result[resultIndex++] = convertedData;
 341                     //  Add the size to the converted data field.
 342                     if(convertedData != null) {
 343                         convertedDataIndex -= 4;
 344                         convertedData[0] = (byte)(convertedDataIndex >> 24);
 345                         convertedData[1] = (byte)(convertedDataIndex >> 16);
 346                         convertedData[2] = (byte)(convertedDataIndex >> 8);
 347                         convertedData[3] = (byte)convertedDataIndex;
 348                     }
 349 
 350                     if(resultIndex >= result.length) {
 351                         Object[] newResult = new Object[result.length * 2];
 352 
 353                         System.arraycopy(result, 0, newResult, 0,
 354                                          result.length);
 355                         result = newResult;
 356                     }
 357                 }
 358 
 359                 if (theChar.fontDescriptor.useUnicode()) {
 360                     convertedData = new byte[(end - stringIndex + 1) *
 361                                         (int)theChar.fontDescriptor.unicodeEncoder.maxBytesPerChar()
 362                                         + 4];
 363                 }
 364                 else  {
 365                     convertedData = new byte[(end - stringIndex + 1) *
 366                                         (int)theChar.fontDescriptor.encoder.maxBytesPerChar()
 367                                         + 4];
 368                 }
 369 
 370                 convertedDataIndex = 4;
 371 
 372                 lastFontDescriptor = theChar.fontDescriptor;
 373             }
 374 
 375             byte[] ba = theChar.bb.array();
 376             int size = theChar.bb.position();
 377             if(size == 1) {
 378                 convertedData[convertedDataIndex++] = ba[0];
 379             }
 380             else if(size == 2) {
 381                 convertedData[convertedDataIndex++] = ba[0];
 382                 convertedData[convertedDataIndex++] = ba[1];
 383             } else if(size == 3) {
 384                 convertedData[convertedDataIndex++] = ba[0];
 385                 convertedData[convertedDataIndex++] = ba[1];
 386                 convertedData[convertedDataIndex++] = ba[2];
 387             } else if(size == 4) {
 388                 convertedData[convertedDataIndex++] = ba[0];
 389                 convertedData[convertedDataIndex++] = ba[1];
 390                 convertedData[convertedDataIndex++] = ba[2];
 391                 convertedData[convertedDataIndex++] = ba[3];
 392             }
 393             stringIndex++;
 394         }
 395 
 396         result[resultIndex++] = lastFontDescriptor;
 397         result[resultIndex] = convertedData;
 398 
 399         //  Add the size to the converted data field.
 400         if(convertedData != null) {
 401             convertedDataIndex -= 4;
 402             convertedData[0] = (byte)(convertedDataIndex >> 24);
 403             convertedData[1] = (byte)(convertedDataIndex >> 16);
 404             convertedData[2] = (byte)(convertedDataIndex >> 8);
 405             convertedData[3] = (byte)convertedDataIndex;
 406         }
 407         return result;
 408     }
 409 
 410     /*
 411      * Create fontCache on demand instead of during construction to
 412      * reduce overall memory consumption.
 413      *
 414      * This method is declared final so that its code can be inlined
 415      * by the compiler.
 416      */
 417     protected final Object[] getFontCache() {
 418         // This method is not MT-safe by design. Since this is just a
 419         // cache anyways, it's okay if we occasionally allocate the array
 420         // twice or return an array which will be dereferenced and gced
 421         // right away.
 422         if (fontCache == null) {
 423             fontCache = new Object[PlatformFont.FONTCACHESIZE];
 424         }
 425 
 426         return fontCache;
 427     }
 428 
 429     /**
 430      * Initialize JNI field and method IDs
 431      */
 432     private static native void initIDs();
 433 
 434     class PlatformFontCache
 435     {
 436         char uniChar;
 437         FontDescriptor fontDescriptor;
 438         ByteBuffer bb = ByteBuffer.allocate(4);
 439     }
 440 }