/* * This file is modified by Ivan Maidanski <ivmai@ivmaisoft.com> * Project name: JCGO-SUNAWT (http://www.ivmaisoft.com/jcgo/) */ /* * @(#)SunGraphicsEnvironment.java 1.109 03/01/23 * * Copyright 2003 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ package sun.java2d; import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Insets; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.font.TextAttribute; import java.awt.image.BufferedImage; import java.awt.print.PrinterJob; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FilenameFilter; import java.io.InputStreamReader; import java.io.IOException; import java.text.AttributedCharacterIterator; import java.util.ArrayList; import java.util.HashSet; import java.util.Hashtable; import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.StringTokenizer; import java.util.TreeMap; import java.util.Vector; import sun.awt.FontProperties; import sun.awt.font.NativeFontWrapper; import sun.awt.image.BufImgSurfaceData; import sun.awt.AppContext; /** * This is an implementation of a GraphicsEnvironment object for the * default local GraphicsEnvironment. * * @see GraphicsDevice * @see GraphicsConfiguration * @version 1.109 01/23/03 */ public abstract class SunGraphicsEnvironment extends GraphicsEnvironment implements FontSupport { protected static boolean debugMapping = false; private static Font defaultFont; private static String [] logicalFontNames = { "default", "serif", "sansserif", "monospaced", "dialog", "dialoginput", }; private FontProperties fprops; private TreeMap terminalNames; private HashSet physicalNames; private boolean loadedAllFonts; protected boolean registeredAllPaths = false; protected boolean noType1Font = false; protected String fontPath; protected TreeMap registeredFonts; private Hashtable mapFamilyCache; protected boolean loadNativeFonts = false; private ArrayList badFonts; public SunGraphicsEnvironment() { loadedAllFonts = false; registeredFonts = new TreeMap(); java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { public Object run() { if (System.getProperty("sun.java2d.debugfonts") != null) { debugMapping = true; } fontPath = System.getProperty("sun.java2d.fontpath", ""); if (fontPath != null && !fontPath.equals("")) registeredAllPaths = true; String defaultPathName = System.getProperty("java.home","") + File.separator + "lib" + File.separator + "fonts"; File badFontFile = new File(defaultPathName + File.separator+ "badfonts.txt"); if (badFontFile.exists()) { FileInputStream fis = null; try { badFonts = new ArrayList(); fis = new FileInputStream(badFontFile); InputStreamReader isr = new InputStreamReader(fis); BufferedReader br = new BufferedReader(isr); while (true) { String name = br.readLine(); if (name == null) { break; } else { if (debugMapping) { System.out.println("read bad font "+ name); } badFonts.add(name); } } } catch (IOException e) { try { if (fis != null) { fis.close(); } } catch (IOException ioe) { } } } /* Install the JRE fonts so that the native platform * can access them. */ registerFontsWithPlatform(defaultPathName); fprops = createFontProperties(); /* use "appendedfontpath" in font.properties file to add additional fontpath(s) */ String appendedPathName = null; if (fprops != null){ appendedPathName = fprops.getProperty("appendedfontpath"); } if (fontPath.length() == 0) { String prop = System.getProperty("sun.java2d.noType1Font"); if (prop == null) { noType1Font = NativeFontWrapper.getType1FontVar(); } if ("true".equals(prop)) { noType1Font = true; } loadNativeFonts = true; fontPath = getBasePlatformFontPath(noType1Font); fontPath = defaultPathName + File.pathSeparator + fontPath; if (appendedPathName != null){ fontPath = fontPath + File.pathSeparator + appendedPathName; } else if (!registeredAllPaths) { fontPath = fontPath + File.pathSeparator + getPlatformFontPath(noType1Font); } if (debugMapping) { System.out.println("Platform Font Path=" + fontPath); } } /* On windows the registerFontPaths method does nothing. * Solaris specific notes (nowhere better to document) :- * register just the paths, (it doesn't register the fonts). * This call was being made already when the * composite font building code needed to find a file * for a composite font entry. Its just been moved up * front to make things more obvious. * We register all the paths on Solaris, because * the fontPath we have here is the complete one from * parsing /var/sadm/install/contents, not just * what's on the X font path (may be this should be * changed). * But for now what it means is that if we didn't do * this then if the font weren't listed anywhere on the * less complete font path we'd trigger loadFonts which * actually registers the fonts. This may actually be * the right thing tho' since that would also set up * the X font path without which we wouldn't be able to * display some "native" fonts. * So something to revisit is that probably fontPath * here ought to be only the X font path + jre font dir. * loadFonts should have a separate native call to * get the rest of the platform font path. */ registerFontPaths(fontPath); /* * Here we get the fonts from the library and register them * so they are always available and preferred over other fonts. * This needs to be registered before the composite fonts as * otherwise some native font that corresponds may be found * instead for Lucida Sans which we now (since 1.4 beta 2) * implicitly add as the last component of all composite fonts. * Note that the jre fonts dir is now pre-pended to the font * path too. Pass "true" to registerFonts method as these * JRE fonts always go through the T2K rasteriser. */ registerFonts(defaultPathName, true); initCompositeFonts(fprops); /* Add the fonts already registered : typically the JRE fonts * and the platform fonts which comprise the composite fonts, * into the terminal names map. Failing to do so triggers * loadfonts() when they are referenced. Note that platform * "native" fonts which we may not choose to expose may be * added but this is harmless since the map is used only to * decide whether to call loadfonts(). It is independent of * those listed by getAllFonts(). * This has no effect if the font name isn't a full * path name - so it works on solaris but not on windows. * So on windows "physicalNames" serves the same purpose * This needs rewriting to have just one way of doing it. */ Object [] regFonts = registeredFonts.keySet().toArray(); for (int i=0; i<regFonts.length; i++) { String key = (String)regFonts[i]; String name = NativeFontWrapper.getFullNameByFileName(key); if (name != null) { name = name.toLowerCase(); } if (name != null && !terminalNames.containsKey(name)) { terminalNames.put(name, new Integer(0)); } } return null; } }); } protected String getBasePlatformFontPath(boolean noType1Font) { return getPlatformFontPath(noType1Font); } protected String getPlatformFontPath(boolean noType1Font) { registeredAllPaths = true; return NativeFontWrapper.getFontPath(noType1Font); } protected GraphicsDevice[] screens; protected synchronized void loadFonts() { if (loadedAllFonts) { return; } if (debugMapping) { System.out.println("loadfonts called"); } final String newPath; if (!registeredAllPaths) { newPath = getPlatformFontPath(noType1Font); registerFontPaths(newPath); /* Results of next line will not be used by runtime code, but * may be clearer when debugging to know the full path */ fontPath = fontPath + File.separator + newPath; } else { newPath = fontPath; } java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { public Object run() { // this will find all fonts including those already // registered. But we have checks in place to prevent // double registration. boolean foundFonts = registerFonts(newPath, false); boolean foundNativeFonts = false; if (loadNativeFonts) { foundNativeFonts = registerNativeFonts (); } // Needed to add the test for existing # of registered // fonts being zero, else this would have caused a JRE // exits if no NEW fonts were registered. if ((!foundFonts) && (!foundNativeFonts) && registeredFonts.size() == 0) { //- REMIND adg: need to throw an exception System.out.println("\nNo fonts were found in '" + newPath + "'.\n"); System.exit(2); } loadedAllFonts = true; return null; } }); } /** * Returns an array of all of the screen devices. */ public synchronized GraphicsDevice[] getScreenDevices() { GraphicsDevice[] ret = screens; if (ret == null) { int num = getNumScreens(); ret = new GraphicsDevice[num]; for (int i = 0; i < num; i++) { ret[i] = makeScreenDevice(i); } screens = ret; } return ret; } protected abstract int getNumScreens(); protected abstract GraphicsDevice makeScreenDevice(int screennum); /** * Returns the default screen graphics device. */ public GraphicsDevice getDefaultScreenDevice() { return getScreenDevices()[0]; } /** * Returns a Graphics2D object for rendering into the * given BufferedImage. * @throws NullPointerException if BufferedImage argument is null */ public Graphics2D createGraphics(BufferedImage img) { if (img == null) { throw new NullPointerException("BufferedImage cannot be null"); } SurfaceData sd = BufImgSurfaceData.createData(img); if (defaultFont == null) { defaultFont = new Font("Dialog", Font.PLAIN, 12); } return new SunGraphics2D(sd, Color.white, Color.black, defaultFont); } private Font[] allFonts; /** * Returns all fonts available in this environment. */ public Font[] getAllFonts() { if (allFonts != null) { return allFonts; } loadFonts(); Font [] fonts = null; String [] fontNames = null; int count = NativeFontWrapper.getNumFonts(); if (count > 0) { TreeMap fontMapNames = new TreeMap(); for (int i=0; i < count; i++) { String name = NativeFontWrapper.getFullNameByIndex(i); if (! name.startsWith(PLSF_PREFIX)) { fontMapNames.put(name, null); } } if (fontMapNames.size() > 0) { fontNames = new String[fontMapNames.size()]; Object [] keyNames = fontMapNames.keySet().toArray(); for (int i=0; i < keyNames.length; i++) { fontNames[i] = (String)keyNames[i]; } } } if (fontNames != null) { fonts = new Font[fontNames.length]; for (int i=0; i < fontNames.length; i++) { fonts[i] = new Font(fontNames[i], Font.PLAIN, 1); } } allFonts = fonts; return allFonts; } public String[] getAvailableFontFamilyNames(Locale theLocale) { // Need to allow for a null locale, as specified in doc if (theLocale == null) { return getAvailableFontFamilyNames(); } loadFonts(); String [] retval = null; int count = NativeFontWrapper.getNumFonts(); int localeID = NativeFontWrapper.getLCIDFromLocale(theLocale); if (count > 0) { TreeMap familyNames = new TreeMap(); for (int i=0; i < count; i++) { String name = NativeFontWrapper.getFamilyNameByIndex(i, localeID); String cmpName = name.toLowerCase(); if ( cmpName.endsWith( ".bold" ) || cmpName.endsWith( ".bolditalic" ) || cmpName.endsWith ( ".italic" ) || cmpName.startsWith ( PLSF_PREFIX )) { } else { familyNames.put(cmpName, name); } } String str; // compatibility str = "Serif"; familyNames.put(str.toLowerCase(), str); str = "SansSerif"; familyNames.put(str.toLowerCase(), str); str = "Monospaced"; familyNames.put(str.toLowerCase(), str); str = "Dialog"; familyNames.put(str.toLowerCase(), str); str = "DialogInput"; familyNames.put(str.toLowerCase(), str); str = "Default"; familyNames.put(str.toLowerCase(), str); if (familyNames.size() > 0) { retval = new String[familyNames.size()]; Object [] keyNames = familyNames.keySet().toArray(); for (int i=0; i < keyNames.length; i++) { retval[i] = (String)familyNames.get(keyNames[i]); } } } return retval; } public String[] getAvailableFontFamilyNames() { return getAvailableFontFamilyNames(Locale.getDefault()); } // implements FontSupport.mapFontName public String mapFontName(String fontName, int style) { // try the cache first String mappedName = (String) mapFontCache.get(fontName + "." + styleStr(style)); if (mappedName != null) { if (fprops.supportPLSF() || fallbackFont != null) { return getInternalFontName(mappedName); } return mappedName; } String lowerCaseName = fontName.toLowerCase(Locale.ENGLISH); // The check below is just so that the bitmap fonts being set by // AWT and Swing thru the desktop properties do not trigger the // the load fonts case. The two bitmap fonts are now mapped to // appropriate equivalents for serif and sansserif. // Also check for a few common misspellings of sansserif. if (lowerCaseName.equals("sanserif") || lowerCaseName.equals("san serif") || lowerCaseName.equals("sans serif") || lowerCaseName.equals("ms sans serif")) { lowerCaseName = "sansserif"; } else if (lowerCaseName.equals("ms serif" )) { lowerCaseName = "serif"; } // Check whether we have a logical font family name if (FontProperties.isLogicalFontFamilyName(lowerCaseName)) { mappedName = getLogicalFontFaceName(lowerCaseName, style); } // Check whether an alias is defined for the name if (mappedName == null) { String aliasName = fprops.getAliasedFamilyName(lowerCaseName); if (aliasName != null) { mappedName = getLogicalFontFaceName(aliasName, style); } } // Look for a physical font name, and use fallback font if no physical font exists if (mappedName == null) { // If the font name is one of the JDK 1.0 logical font names, we may want to // use special fallback mappings if no physical font exists. Therefore, // we need to be precise in looking for physical fonts, i.e., go to the native // level which has more complete information about physical fonts. // REMIND: remove this compatibility workaround from the next feature release. if (fprops.getFallbackFamilyName(lowerCaseName, null) != null) { // Check whether a font is registered to support this font/style // combination. At this point, this would normally be a physical font. // We may do this check twice, once without, once with loadFonts, since // loadFonts can be quite expensive, and the font may already have // been registered as the component of a logical font. // Also, we don't want to synchronize this entire section, so // we need to have the check on loadedAllFonts first. if (loadedAllFonts) { if (NativeFontWrapper.isFontRegistered(fontName, style)) { mappedName = fontName; } } else { if (NativeFontWrapper.isFontRegistered(fontName, style)) { mappedName = fontName; } else { if (debugMapping) { System.out.println("calling loadFonts to find font " + fontName); } loadFonts(); if (NativeFontWrapper.isFontRegistered(fontName, style)) { mappedName = fontName; } } } // Apply a fallback mapping if there is no physical font. if (mappedName == null) { String fallbackName = fprops.getFallbackFamilyName(lowerCaseName, "dialog"); mappedName = getLogicalFontFaceName(fallbackName, style); } } else { // For all other font names, we don't need to do a precise lookup here. // We're checking whether the font name has already been registered as // as the component font of a logical font in order to avoid unnecessary // calls to loadFonts. But if we don't find the font here, we just use // the given font name, make sure all fonts have been loaded, and let // initializeFont (called from the Font constructor) deal with a precise // lookup and, if necessary, the fallback to Dialog. if (!terminalNames.containsKey(lowerCaseName) && (physicalNames == null || (!physicalNames.contains(lowerCaseName)))) { if (debugMapping) { System.out.println("calling loadFonts to find font " + fontName); } loadFonts(); } mappedName = fontName; } } //assertion: mappedName != null; // cache and return the result if (debugMapping) { System.out.println("mapped font " + fontName + " (" + styleStr(style) + ") " + " to " + mappedName); } mapFontCache.put(fontName + "." + styleStr(style), mappedName); if (fprops.supportPLSF() || fallbackFont != null) { if (debugMapping) { String newName = getInternalFontName(mappedName); System.out.println("=============================================================="); System.out.println("originalName =" + mappedName + ", localeName=" + newName); System.out.println("currentThread=" + Thread.currentThread()); return newName; } else { return getInternalFontName(mappedName); } } return mappedName; } private String getLogicalFontFaceName(String familyName, int style) { String fullName = familyName.toLowerCase(Locale.ENGLISH) + "." + styleStr(style); if (terminalNames.containsKey(fullName)) { return fullName; } return familyName; } private static Hashtable mapFontCache = new Hashtable(5, (float) 0.9); // can have platform-specific overrides - see X11GraphicsEnvironment protected String parseFamilyNameProperty(String name) { int separator = name.indexOf(","); if (separator == -1) { separator = name.length(); } return name.substring(0, separator); } // can have platform-specific overrides - see X11GraphicsEnvironment protected String getFontPropertyFD(String name) { return parseFamilyNameProperty(name); } // can have platform-specific overrides - see X11GraphicsEnvironment protected String getFileNameFromPlatformName(String platName) { if (fprops == null) { return null; } platName = platName.replace(' ','_'); return fprops.getProperty("filename" + "." + platName); } /** * Gets a <code>PrintJob2D</code> object suitable for the * the current platform. * @return a <code>PrintJob2D</code> object. * @see java.awt.PrintJob2D * @since JDK1.2 */ public PrinterJob getPrinterJob() { new Exception().printStackTrace(); return null; } /* MACPORTING NOTE.needs to do file type on the Macintosh */ // adg: ttc files are now handled by the ttf code public class TTFilter implements FilenameFilter{ public boolean accept(File dir,String name) { return(name.endsWith(".ttf") || name.endsWith(".TTF") || name.endsWith(".ttc") || name.endsWith(".TTC")); } } public class T2KFilter implements FilenameFilter{ public boolean accept(File dir,String name) { return(name.endsWith(".t2k") || name.endsWith(".T2K")); } } public class T1Filter implements FilenameFilter{ public boolean accept(File dir,String name) { return(name.endsWith(".ps") || name.endsWith(".PS") || name.endsWith(".pfb") || name.endsWith(".PFB") || name.endsWith(".pfa") || name.endsWith(".PFA")); } } /* The majority of the register functions in this class are * registering platform fonts in the JRE's font maps. * The next one is opposite in function as it registers the JRE * fonts as platform fonts. If subsequent to calling this * your implementation enumerates platform fonts in a way that * would return these fonts too you may get duplicates. * This function is primarily used to install the JRE fonts * so that the native platform can access them. * It is intended to be overridden by platform subclasses * Currently minimal use is made of this as generally * Java 2D doesn't need the platform to be able to * use its fonts and platforms which already have matching * fonts registered (possibly even from other different JRE * versions) generally can't be guaranteed to use the * one registered by this JRE version in response to * requests from this JRE. */ protected void registerFontsWithPlatform(String pathName) { return; } protected void registerFontPaths(String pathName) { return; } private boolean registerFonts(String pathName, boolean useJavaRasterizer) { boolean retval = false; StringTokenizer parser = new StringTokenizer(pathName, File.pathSeparator); try { while (parser.hasMoreTokens()) { String newPath = parser.nextToken(); // paths now registered in constructor. // registerFontPath(newPath); retval |= addPathFonts(newPath, new TTFilter(), NativeFontWrapper.FONTFORMAT_TRUETYPE, useJavaRasterizer); retval |= addPathFonts(newPath, new T1Filter(), NativeFontWrapper.FONTFORMAT_TYPE1, useJavaRasterizer); retval |= addPathFonts(newPath, new T2KFilter(), NativeFontWrapper.FONTFORMAT_T2K, useJavaRasterizer); } } catch (NoSuchElementException e) { System.err.println(e); } return retval; } // ** REMIND : VERIFY WHAT THIS DOES ON WINDOWS // It appears this method is used only on windows // On solaris it it were called it would be passed a full path name // which is clearly not what its expecting. // If it gets just a file name on windows that would "work" but be // really bad code. If it gets a full path then I don't see how it // could possibly work. // can have platform specific override protected void registerFontFile(String fontFileName, Vector nativeNames) { // REMIND: case compare depends on platform if (registeredFonts.containsKey(fontFileName)) { return; } int fontFormat; if (new TTFilter().accept(null, fontFileName)) { fontFormat = NativeFontWrapper.FONTFORMAT_TRUETYPE; } else if (new T1Filter().accept(null, fontFileName)) { fontFormat = NativeFontWrapper.FONTFORMAT_TYPE1; } else if (new T2KFilter().accept(null, fontFileName)) { fontFormat = NativeFontWrapper.FONTFORMAT_T2K; } else { registerNative (fontFileName); return; } StringTokenizer parser = new StringTokenizer(fontPath, File.pathSeparator); try { while (parser.hasMoreTokens()) { String newPath = parser.nextToken(); File theFile = new File(newPath, fontFileName); String path = null; try { path = theFile.getCanonicalPath(); } catch (IOException e) { path = theFile.getAbsolutePath(); } if (theFile.canRead()) { Vector fontNames = new Vector(1, 1); Vector platNames = new Vector(1, 1); platNames.addElement(nativeNames); fontNames.addElement(path); registeredFonts.put(fontFileName, path); NativeFontWrapper.registerFonts(fontNames, fontNames.size(), platNames, fontFormat, false); /* We note the physical fonts registered for font * properties so that we can add these to the * set searched before calling loadfonts() */ if (physicalNames == null) { physicalNames = new HashSet(); } String name = NativeFontWrapper.getFullNameByFileName(path); if (name != null) { name = name.toLowerCase(); } if (!physicalNames.contains(name)) { physicalNames.add(name); } break; } } } catch (NoSuchElementException e) { System.err.println(e); } } // can have platform specific override protected void registerFontPath(String path) { } protected void registerNative (String fontFileName) { } protected Vector getNativeNames (String fontFileName) { Vector v = new Vector(); //v.add(fontFileName); return v; } protected boolean registerNativeFonts () { return false; } /* * helper function for registerFonts */ private boolean addPathFonts(String path, FilenameFilter filter, int fontFormat, boolean useJavaRasterizer) { boolean retval = false; Vector fontNames = new Vector(20, 10); Vector nativeNames = new Vector(20,10); File f1 = new File(path); String[] ls = f1.list(filter); if (ls == null) { return retval; } for (int i=0; i < ls.length; i++ ) { File theFile = new File(f1, ls[i]); String fullName = null; try { fullName = theFile.getCanonicalPath(); } catch (IOException e) { fullName = theFile.getAbsolutePath(); } // REMIND: case compare depends on platform if (registeredFonts.containsKey(fullName)) { continue; } if (badFonts != null && badFonts.contains(fullName)) { if (debugMapping) { System.out.println("skip bad font " + fullName); } continue; // skip this font file. } registeredFonts.put(fullName, fullName); if (debugMapping) { System.out.println("Registering font " + fullName); Vector v = getNativeNames(fullName); if (v.size() == 0) { System.out.println("No native name"); } else { for (int nn=0; nn< v.size(); nn++) { System.out.println("native name : " + (String)v.elementAt(nn)); } } } fontNames.addElement(fullName); nativeNames.addElement (getNativeNames(fullName)); retval = true; } // REMIND - native code might not register everything which we // pass into it. NativeFontWrapper.registerFonts(fontNames, fontNames.size(), nativeNames, fontFormat, useJavaRasterizer ); return retval; // REMIND: get status of registration from native } /** * Resolve styles on the character at start into an instance of Font * that can best render the text between start and limit. * REMIND jk. Move it to graphics environment. */ public static Font getBestFontFor(AttributedCharacterIterator text, int start, int limit) { /* * choose the first font that can display the first character * first iterate through the styles in the range of text we were * passed. If none of them work, iterate through font families * using the attributes on the first character. If this also * fails, use the first font. */ char c = text.setIndex(start); Map ff = text.getAttributes(); Font font = Font.getFont(ff); while (!font.canDisplay(c) && (text.getRunLimit() < limit)) { text.setIndex(text.getRunLimit()); font = Font.getFont(text.getAttributes()); } if (!font.canDisplay(c)) { text.setIndex(start); String[] families = GraphicsEnvironment.getLocalGraphicsEnvironment( ).getAvailableFontFamilyNames(); for (int i = 0; i < families.length; ++i) { Hashtable ht = new Hashtable(); ht.putAll(ff); ht.put(TextAttribute.FAMILY, families[i]); font = Font.getFont((Map)ht); if (font.canDisplay(c)) { break; } } if (!font.canDisplay(c)) { font = Font.getFont(ff); } } return font; } /** * Creates this environment's FontProperties. */ protected abstract FontProperties createFontProperties(); private void initCompositeFonts(FontProperties fprops) { TreeMap terminalNames = initTerminalNames(fprops); Object [] terminalKeys = terminalNames.keySet().toArray(); for (int i=0; i < terminalKeys.length; i++) { String compositeFontName = (String)terminalKeys[i]; Integer maxEntryInt = (Integer)terminalNames.get(terminalKeys[i]); int numEntries = maxEntryInt.intValue(); // Check to see if the Lucida Sans Regular is already in the // font.properties as an entry. If it is do not add it to the // list at the bottom as it would never be used. boolean containsLucida = false; for (int entries=0; entries < numEntries; entries++) { String entryName = parseFamilyNameProperty( fprops.getProperty( compositeFontName + "." + entries)); if (entryName.compareToIgnoreCase("Lucida Sans Regular")==0) { containsLucida = true; break; } } // Add an entry for Lucida Sans Regular if ( containsLucida == false ) { numEntries++; // one for the Lucida fallback } if (fallbackFont != null && this.terminalNames.containsKey(fallbackFont.toLowerCase(Locale.ENGLISH))) { numEntries++; //for the font specified by setFallbackFont(); } String names[] = new String[numEntries]; int exclusionMaxIndex[] = new int[numEntries]; int exclusionRanges[] = new int[0]; int totalEntries = numEntries; if ( containsLucida == false ) { // Add the Lucida Sans Regular font here for richer glyph // availablity in the logical fonts. This enables Dingbats // and Symbols glyphs. names[numEntries - 1] = "Lucida Sans Regular"; totalEntries--; } if (fallbackFont != null && this.terminalNames.containsKey(fallbackFont.toLowerCase(Locale.ENGLISH))) { if ( containsLucida == false ) { names[numEntries - 2] = "Lucida Sans Regular"; } names[numEntries - 1] = fallbackFont; totalEntries--; } for (int j=0; j < totalEntries; j++) { names[j] = parseFamilyNameProperty( fprops.getProperty( compositeFontName + "." + j)); if (debugMapping) { System.out.println ( "The composite name = " + names[j] ); } exclusionRanges = appendExclusions(fprops, compositeFontName, j, exclusionRanges); exclusionMaxIndex[j] = exclusionRanges.length; } if (debugMapping) { System.out.println("initCompositeFonts compositeFontName="+ compositeFontName); } if (initPLSFFallback){ compositeFontName = prefixPLSF + compositeFontName; } if (debugMapping) { System.out.println("registerCompositeFont:" + compositeFontName); for (int j=0; j < numEntries; j++) { System.out.println(" slot=" + names[j]); } } NativeFontWrapper.registerCompositeFont( compositeFontName, names, exclusionRanges, exclusionMaxIndex); } terminalNames.put("default",new Integer(0)); if (!initPLSFFallback) { //don't update the table if its not the first time this.terminalNames = terminalNames; } } private int[] appendExclusions(FontProperties fprops, String name, int slot, int [] ranges) { // We check for exclusions first with family name and style, then // family name only. String familyName; String styleName; int period = name.indexOf('.'); if (period > 0) { familyName = name.substring(0, period); styleName = name.substring(period + 1); } else { familyName = name; styleName = "plain"; } String exclusions = fprops.getProperty( "exclusion." + familyName + "." + styleName + "." + slot); if (exclusions == null) { exclusions = fprops.getProperty( "exclusion." + familyName + "." + slot); } // REMIND: invent exclusion ranges for dingbats and symbols // since no properties files specify them // (or fix all properties files --- better) if (exclusions != null) { /* * range format is xxxx-XXXX,yyyy-YYYY,..... */ int numExclusions = (exclusions.length() + 1) / 10; if (numExclusions > 0) { int newRanges[] = new int[numExclusions * 2]; for (int i = 0; i < numExclusions; i++) { String lower = exclusions.substring(i*10 , i*10 + 4); String upper = exclusions.substring(i*10 + 5, i*10 + 9); newRanges[i*2 ] = Integer.parseInt(lower, 16); newRanges[i*2+1] = Integer.parseInt(upper, 16); } int totalRanges = ranges.length + newRanges.length; int tempRanges[] = new int[totalRanges]; System.arraycopy( ranges, 0, tempRanges, 0, ranges.length); System.arraycopy( newRanges, 0, tempRanges, ranges.length, newRanges.length); ranges = tempRanges; } } return ranges; } private TreeMap initTerminalNames(FontProperties fprops) { TreeMap predefinedNames = new TreeMap(); TreeMap registeredFileNames = new TreeMap(); TreeMap terminalNames = new TreeMap(); addPlatformCompatibilityFileNames(registeredFileNames); String str; // compatibility str = "Serif"; predefinedNames.put(str.toLowerCase(Locale.ENGLISH), str); str = "SansSerif"; predefinedNames.put(str.toLowerCase(Locale.ENGLISH), str); str = "Monospaced"; predefinedNames.put(str.toLowerCase(Locale.ENGLISH), str); str = "Dialog"; predefinedNames.put(str.toLowerCase(Locale.ENGLISH), str); str = "DialogInput"; predefinedNames.put(str.toLowerCase(Locale.ENGLISH), str); if (fprops == null) throw new Error("no font properties file found."); Object [] propKeys = fprops.keySet().toArray(); for (int i=0; i < propKeys.length; i++) { // discard keys which aren't predefined font family names String property = (String)propKeys[i]; int separator = property.indexOf("."); if (separator == -1) { separator = property.length(); } String propFamily = property.substring(0, separator); if (!predefinedNames.containsKey(propFamily)) { continue; // discard, not predefined } // find out how many entries for this key separator = property.lastIndexOf("."); if (separator == -1) { continue; // discard, invalid format } String familyStyle = property.substring(0, separator); if (terminalNames.containsKey(familyStyle)) { continue; // discard, already analyzed } if (!fprops.containsKey(familyStyle + ".0")) { continue; // discard, invalid file format } int maxEntry = 0; while (fprops.containsKey(familyStyle + "." + maxEntry)) { maxEntry++; } if (maxEntry == 0) { continue; // discard, want direct mapping } terminalNames.put(familyStyle, new Integer(maxEntry)); if (debugMapping) { System.out.println("FamilyStyle: " + familyStyle); System.out.println("NumSlots: " + maxEntry); System.out.println("Key: " + (String)propKeys[i]); } for (int j=0; j < maxEntry; j++) { String platName = getFontPropertyFD( fprops.getProperty( familyStyle + "." + j)); if (!initPLSFFallback) { //needed only the first time? ? ? addPlatformNameForFontProperties(platName); } String fontFileName = getFileNameFromPlatformName(platName); if (debugMapping) { System.out.println("FS: [" + familyStyle + "." + j + "] PN: [" + platName + "] FN: [" + fontFileName + "]"); } if (fontFileName == null) { // invalid configuration file(s), but only warn if // is debugging. Usually saves xterminal users from // being told they don't have sun dingbats fonts. if (debugMapping) { System.err.println( "Font specified in font.properties not found [" + platName + "]"); } // on headless loadfonts was being triggered // every time because the native only fonts were // not returning null for fontFileName. // So now do only if local & headful. loadFonts(); // was "break" here - why? } else { // A font file may occur more than once in font // properties. It may appear with the same or different // platform/native names - particularly on X11 where // each encoding is a different platform name. // We could just ask for the native names here and // register those except that the font properties // files have carefully crafted strings ready for // a simple sprintf of the required pt size. // We'd like to register those as our native names // rather than the ones returned from X // This is somewhat fiddly as we need to first gather // all these used in the font properties file and // associate them all with the same font. // That needs to be delegated to the platform subclas // as equating the native names needs to be done there. HashSet s = (HashSet)registeredFileNames.get(fontFileName); if (s == null) { s = new HashSet(); s.add(platName); registeredFileNames.put(fontFileName, s); } else { if (!s.contains(platName)) { s.add(platName); } } } } } if (!initPLSFFallback) { //needed only the first time? ? ? registerFontPropertiesFonts(registeredFileNames); } return terminalNames; } /** * Adds entries to registeredFileNames for fonts that should be * preferred when looking for physical fonts. Each entry has a file name * as its key and a HashSet with the platform names of fonts in the file * as its value. * REMIND: remove this method and references to it from the next feature release. */ protected void addPlatformCompatibilityFileNames(Map registeredFileNames) { } protected void addPlatformNameForFontProperties(String platName) { return; } // this method accepts a TreeMap where either // - the keys are font file names which may be or may not be full // path names, and the value is a possibly empty set of native names // - or the key and value are native names. protected void registerFontPropertiesFonts(TreeMap fPropFonts) { Object [] fonts = fPropFonts.keySet().toArray(); for (int i=0; i<fonts.length; i++) { String fontFileName = (String)fonts[i]; HashSet s = (HashSet)fPropFonts.get(fontFileName); String[] platNames = (String[])s.toArray(new String[0]); Vector nativeNames = getNativeNames(fontFileName); // merge the platNames & nativeNames. for (int j=0;j<platNames.length;j++) { if (!nativeNames.contains(platNames[j])) { nativeNames.add(platNames[j]); } } registerFontFile(fontFileName, nativeNames); } } /* * return String representation of style */ public static String styleStr(int num){ switch(num){ case Font.BOLD: return "bold"; case Font.ITALIC: return "italic"; case Font.ITALIC | Font.BOLD: return "bolditalic"; default: return "plain"; } } public static boolean isLogicalFont(Font f) { String name = f.getFamily(); return isLogicalFont(name); } public static boolean isLogicalFont(String name) { name = name.toLowerCase(Locale.ENGLISH); for (int i=0; i<logicalFontNames.length; i++) { if (name.equals(logicalFontNames[i])) { return true; } } return false; } public static String createFont(File fontFile) { return NativeFontWrapper.createFont(fontFile.getAbsolutePath(), NativeFontWrapper.FONTFORMAT_TRUETYPE); } /** * Return the current font properties. */ public FontProperties getFontProperties() { if (!FPAName.equals(AppContext.getAppContext().get(FPAKey)) || fpropsPLSF == null) { return fprops; } return fpropsPLSF; } /** * Return the bounds of a GraphicsDevice, less its screen insets. * See also java.awt.GraphicsEnvironment.getUsableBounds(); */ public static Rectangle getUsableBounds(GraphicsDevice gd) { GraphicsConfiguration gc = gd.getDefaultConfiguration(); Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(gc); Rectangle usableBounds = gc.getBounds(); usableBounds.x += insets.left; usableBounds.y += insets.top; usableBounds.width -= (insets.left + insets.right); usableBounds.height -= (insets.top + insets.bottom); return usableBounds; } /** * @param font representing a physical font. * @return true if the underlying font is a TrueType or OpenType font * that claims to support the Microsoft Windows encoding corresponding to * the default file.encoding property of this JRE instance. * This narrow value is useful for Swing to decide if the font is useful * for the the Windows Look and Feel, or, if a composite font should be * used instead. * The information used to make the decision is obtained from * the ulCodePageRange fields in the font. * A caller can use isLogicalFont(Font) in this class before calling * this method and would not need to call this method if that * returns true. */ public static boolean fontSupportsDefaultEncoding(Font font) { String encoding = (String) java.security.AccessController.doPrivileged( new sun.security.action.GetPropertyAction("file.encoding")); if (encoding == null || font == null) { return false; } encoding = encoding.toLowerCase(java.util.Locale.ENGLISH); return NativeFontWrapper.fontSupportsEncoding(font, encoding); } /** * Method invoked by applet to prefer "LocaleSpecificFonts" logic * font mapping for the current AppContext. */ public static void preferLocaleSpecificFonts(){ hasPLSF = true; AppContext.getAppContext().put(FPAKey, FPAName); } /** * Method invoked by applet to specify a fallback font for logic * fonts used with in current AppContext */ public static void setFallbackFont(String name) { fallbackFont = name; AppContext.getAppContext().put(FPAKey, FPAName); } //key/value stored in AppContext private static final String FPAKey = "FontPropertiesAttr"; private static final String FPAName = "PLSFFallback"; private static final String PLSF_PREFIX = "_plsf_"; private String prefixPLSF = null; private boolean initPLSFFallback = false; private FontProperties fpropsPLSF = null; protected static boolean hasPLSF = false; protected static String fallbackFont = null; public String getInternalFontName(String orgName){ if (!hasPLSF && fallbackFont == null) { return orgName; } String fpaValue; Object fpaName; //if nothing has been defined in AppContext, return the original name if ((fpaName = AppContext.getAppContext().get(FPAKey)) == null || ! fpaName.equals(FPAName)) { return orgName; } //Return the original name if it's not a logical font String name = orgName.toLowerCase(); boolean isLogicalFont = false; for (int i = 0; i < logicalFontNames.length; i++) { if (name.startsWith(logicalFontNames[i])){ isLogicalFont = true; break; } } if (!isLogicalFont) { return orgName; } if (prefixPLSF != null) { return prefixPLSF + orgName; } synchronized (this){ if (prefixPLSF != null) { return prefixPLSF + orgName; } if (hasPLSF) { fpropsPLSF = fprops.applyPreferLocaleSpecificFonts(fprops); } else { fpropsPLSF = fprops; } initPLSFFallback = true; prefixPLSF = PLSF_PREFIX; initCompositeFonts(fpropsPLSF); return prefixPLSF + orgName; } } }