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      */
  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 
  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      */
 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 
 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 
 235     private static native boolean haveBitmapFonts(byte[] xlfd);
 236     private static native boolean fontExists(byte[] xlfd);
 237 
 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 
 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 
 272     public Rectangle2D getMaxCharBounds(FontRenderContext frc) {
 273             return null;
 274     }
 275 
 276     native StrikeMetrics getFontMetrics(long pScalerContext);
 277 
 278     native float getGlyphAdvance(long pContext, int glyphCode);
 279 
 280     Rectangle2D.Float getGlyphOutlineBounds(long pScalerContext,
 281                                             int glyphCode) {
 282         return new Rectangle2D.Float(0f, 0f, 0f, 0f);
 283     }
 284 
 285     public GeneralPath getGlyphOutline(long pScalerContext,
 286                                        int glyphCode,
 287                                        float x,
 288                                        float y) {
 289         return null;
 290     }
 291 
 292     native long getGlyphImage(long pScalerContext, int glyphCode);
 293 
 294     native long getGlyphImageNoDefault(long pScalerContext, int glyphCode);
 295 
 296     void getGlyphMetrics(long pScalerContext, int glyphCode,
 297                         Point2D.Float metrics) {
 298         throw new RuntimeException("this should be called on the strike");
 299     }
 300 
 301     public  GeneralPath getGlyphVectorOutline(long pScalerContext,
 302                                               int[] glyphs, int numGlyphs,
 303                                               float x,  float y) {
 304         return null;
 305     }
 306 
 307     private native int countGlyphs(byte[] platformNameBytes, int ptSize);
 308 
 309     public int getNumGlyphs() {
 310         if (numGlyphs == -1) {
 311             byte[] bytes = getPlatformNameBytes(8);
 312             numGlyphs = countGlyphs(bytes, 8);
 313         }
 314         return numGlyphs;
 315     }
 316 
 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      */
 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 
 391     public String toString() {
 392         return " ** Native Font: Family="+familyName+ " Name="+fullName+
 393             " style="+style+" nativeName="+platName;
 394     }
 395 }