/*
 * Copyright (c) 2003, 2006, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.font;

import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.GraphicsEnvironment;
import java.awt.font.FontRenderContext;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.UnsupportedEncodingException;
import java.lang.ref.WeakReference;
import java.util.Locale;

/*
 * Ideally there would be no native fonts used, and this class would be
 * unneeded and removed. Presently it is still needed until such time
 * as font configuration files (or the implementation equivalent) can have
 * all references to fonts that are not handled via Java 2D removed.
 * Currently there are two cases where this class is needed, both on
 * Unix, primarily Solaris, but useful on Linux too if fonts have moved.
 * 1. Some legacy F3 fonts are still referenced so that AWT "X/Motif"
 * can get dingbats and symbols from them. This can be dispensed with when
 * either AWT is based on 2D, or when the X font path is known to always
 * contain a Type1 or TrueType font that can be used in font configuration
 * files to replace the F3 fonts.
 * 2. When location of font files by 2D fails, because of some system
 * configuration problem, it is desirable to have a fall back to some
 * functionality that lessens the immediate impact on users. Being able
 * to perform limited operations by using bitmaps from X11 helps here.
 */

public class NativeFont extends PhysicalFont {

    String encoding;

    private int numGlyphs = -1;
    boolean isBitmapDelegate;
    PhysicalFont delegateFont;

    
Verifies native font is accessible.
Throws:
  • FontFormatException – - if the font can't be located.
/** * Verifies native font is accessible. * @throws FontFormatException - if the font can't be located. */
public NativeFont(String platName, boolean bitmapDelegate) throws FontFormatException { super(platName, null); /* This is set true if this is an instance of a NativeFont * created by some other font, to get native bitmaps. * The delegating font will call this font only for "basic" * cases - ie non-rotated, uniform scale, monochrome bitmaps. * If this is false, then this instance may need to itself * delegate to another font for non-basic cases. Since * NativeFonts are used in that way only for symbol and dingbats * we know its safe to delegate these to the JRE's default * physical font (Lucida Sans Regular). */ isBitmapDelegate = bitmapDelegate; if (GraphicsEnvironment.isHeadless()) { throw new FontFormatException("Native font in headless toolkit"); } fontRank = Font2D.NATIVE_RANK; initNames(); if (getNumGlyphs() == 0) { throw new FontFormatException("Couldn't locate font" + platName); } } private void initNames() throws FontFormatException { /* Valid XLFD has exactly 14 "-" chars. * First run over the string to verify have at least this many * At the same time record the locations of the hyphens * so we can just pick the right substring later on */ int[] hPos = new int[14]; int hyphenCnt = 1; int pos = 1; String xlfd = platName.toLowerCase(Locale.ENGLISH); if (xlfd.startsWith("-")) { while (pos != -1 && hyphenCnt < 14) { pos = xlfd.indexOf('-', pos); if (pos != -1) { hPos[hyphenCnt++] = pos; pos++; } } } if (hyphenCnt == 14 && pos != -1) { /* Capitalise words in the Family name */ String tmpFamily = xlfd.substring(hPos[1]+1, hPos[2]); StringBuilder sBuffer = new StringBuilder(tmpFamily); char ch = Character.toUpperCase(sBuffer.charAt(0)); sBuffer.replace(0, 1, String.valueOf(ch)); for (int i=1;i<sBuffer.length()-1; i++) { if (sBuffer.charAt(i) == ' ') { ch = Character.toUpperCase(sBuffer.charAt(i+1)); sBuffer.replace(i+1, i+2, String.valueOf(ch)); } } familyName = sBuffer.toString(); String tmpWeight = xlfd.substring(hPos[2]+1, hPos[3]); String tmpSlant = xlfd.substring(hPos[3]+1, hPos[4]); String styleStr = null; if (tmpWeight.indexOf("bold") >= 0 || tmpWeight.indexOf("demi") >= 0) { style |= Font.BOLD; styleStr = "Bold"; } if (tmpSlant.equals("i") || tmpSlant.indexOf("italic") >= 0) { style |= Font.ITALIC; if (styleStr == null) { styleStr = "Italic"; } else { styleStr = styleStr + " Italic"; } } else if (tmpSlant.equals("o") || tmpSlant.indexOf("oblique") >= 0) { style |= Font.ITALIC; if (styleStr == null) { styleStr = "Oblique"; } else { styleStr = styleStr + " Oblique"; } } if (styleStr == null) { fullName = familyName; } else { fullName = familyName + " " + styleStr; } encoding = xlfd.substring(hPos[12]+1); if (encoding.startsWith("-")) { encoding = xlfd.substring(hPos[13]+1); } if (encoding.indexOf("fontspecific") >= 0) { if (tmpFamily.indexOf("dingbats") >= 0) { encoding = "dingbats"; } else if (tmpFamily.indexOf("symbol") >= 0) { encoding = "symbol"; } else { encoding = "iso8859-1"; } } } else { throw new FontFormatException("Bad native name " + platName); // familyName = "Unknown"; // fullName = "Unknown"; // style = Font.PLAIN; // encoding = "iso8859-1"; } } /* Wildcard all the size fields in the XLFD and retrieve a list of * XLFD's that match. * We only look for scaleable fonts, so we can just replace the 0's * with *'s and see what we get back * No matches means even the scaleable version wasn't found. This is * means the X font path isn't set up for this font at all. * One match means only the scaleable version we started with was found * -monotype-arial-bold-i-normal--0-0-0-0-p-0-iso8859-1 * Two matches apparently means as well as the above, a scaleable * specified for 72 dpi is found, not that there are bitmaps : eg * -monotype-arial-bold-i-normal--0-0-72-72-p-0-iso8859-1 * So require at least 3 matches (no need to parse) to determine that * there are external bitmaps. */ static boolean hasExternalBitmaps(String platName) { /* Turn -monotype-arial-bold-i-normal--0-0-0-0-p-0-iso8859-1 * into -monotype-arial-bold-i-normal--*-*-*-*-p-*-iso8859-1 * by replacing all -0- substrings with -*- */ StringBuilder sb = new StringBuilder(platName); int pos = sb.indexOf("-0-"); while (pos >=0) { sb.replace(pos+1, pos+2, "*"); pos = sb.indexOf("-0-", pos); }; String xlfd = sb.toString(); byte[] bytes = null; try { bytes = xlfd.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { bytes = xlfd.getBytes(); } return haveBitmapFonts(bytes); } public static boolean fontExists(String xlfd) { byte[] bytes = null; try { bytes = xlfd.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { bytes = xlfd.getBytes(); } return fontExists(bytes); } private static native boolean haveBitmapFonts(byte[] xlfd); private static native boolean fontExists(byte[] xlfd); public CharToGlyphMapper getMapper() { if (mapper == null) { if (isBitmapDelegate) { /* we are a delegate */ mapper = new NativeGlyphMapper(this); } else { /* we need to delegate */ delegateFont = FontManager.getDefaultPhysicalFont(); mapper = delegateFont.getMapper(); } } return mapper; } FontStrike createStrike(FontStrikeDesc desc) { if (isBitmapDelegate) { return new NativeStrike(this, desc); } else { if (delegateFont == null) { delegateFont = FontManager.getDefaultPhysicalFont(); } /* If no FileFont's are found, delegate font may be * a NativeFont, so we need to avoid recursing here. */ if (delegateFont instanceof NativeFont) { return new NativeStrike((NativeFont)delegateFont, desc); } FontStrike delegate = delegateFont.createStrike(desc); return new DelegateStrike(this, desc, delegate); } } public Rectangle2D getMaxCharBounds(FontRenderContext frc) { return null; } native StrikeMetrics getFontMetrics(long pScalerContext); native float getGlyphAdvance(long pContext, int glyphCode); Rectangle2D.Float getGlyphOutlineBounds(long pScalerContext, int glyphCode) { return new Rectangle2D.Float(0f, 0f, 0f, 0f); } public GeneralPath getGlyphOutline(long pScalerContext, int glyphCode, float x, float y) { return null; } native long getGlyphImage(long pScalerContext, int glyphCode); native long getGlyphImageNoDefault(long pScalerContext, int glyphCode); void getGlyphMetrics(long pScalerContext, int glyphCode, Point2D.Float metrics) { throw new RuntimeException("this should be called on the strike"); } public GeneralPath getGlyphVectorOutline(long pScalerContext, int[] glyphs, int numGlyphs, float x, float y) { return null; } private native int countGlyphs(byte[] platformNameBytes, int ptSize); public int getNumGlyphs() { if (numGlyphs == -1) { byte[] bytes = getPlatformNameBytes(8); numGlyphs = countGlyphs(bytes, 8); } return numGlyphs; } PhysicalFont getDelegateFont() { if (delegateFont == null) { delegateFont = FontManager.getDefaultPhysicalFont(); } return delegateFont; } /* Specify that the dpi is 72x72, as this corresponds to JDK's * default user space. These are the 10th and 11th fields in the XLFD. * ptSize in XLFD is in 10th's of a point so multiply by 10, * Replace the 9th field in the XLFD (ie after the 8th hyphen) * with this pt size (this corresponds to the field that's "%d" in the * font configuration files). Wild card the other numeric fields. * ie to request 12 pt Times New Roman italic font, use an XLFD like : * -monotype-times new roman-regular-i---*-120-72-72-p-*-iso8859-1 */ byte[] getPlatformNameBytes(int ptSize) { int[] hPos = new int[14]; int hyphenCnt = 1; int pos = 1; while (pos != -1 && hyphenCnt < 14) { pos = platName.indexOf('-', pos); if (pos != -1) { hPos[hyphenCnt++] = pos; pos++; } } String sizeStr = Integer.toString((int)Math.abs(ptSize)*10); StringBuilder sb = new StringBuilder(platName); /* work backwards so as to not invalidate the positions. */ sb.replace(hPos[11]+1, hPos[12], "*"); sb.replace(hPos[9]+1, hPos[10], "72"); sb.replace(hPos[8]+1, hPos[9], "72"); /* replace the 3 lines above with the next 3 lines to get the 1.4.2 * behaviour */ // sb.replace(hPos[11]+1, hPos[12], "0"); // sb.replace(hPos[9]+1, hPos[10], "0"); // sb.replace(hPos[8]+1, hPos[9], "0"); sb.replace(hPos[7]+1, hPos[8], sizeStr); sb.replace(hPos[6]+1, hPos[7], "*"); /* replace the 1 line above with the next line to get the 1.4.2 * behaviour */ // sb.replace(hPos[6]+1, hPos[7], "0"); /* comment out this block to the the 1.4.2 behaviour */ if (hPos[0] == 0 && hPos[1] == 1) { /* null foundry name : some linux font configuration files have * symbol font entries like this and its just plain wrong. * Replace with a wild card. (Although those fonts should be * located via disk access rather than X11). */ sb.replace(hPos[0]+1, hPos[1], "*"); } String xlfd = sb.toString(); byte[] bytes = null; try { bytes = xlfd.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { bytes = xlfd.getBytes(); } return bytes; } public String toString() { return " ** Native Font: Family="+familyName+ " Name="+fullName+ " style="+style+" nativeName="+platName; } }