package org.newdawn.slick.tools.hiero.truetype; import java.awt.Font; import java.awt.FontFormatException; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; /** * A wrapper that brings Java font and TTF read fonts together. * * @author kevin */ public class FontData { /** The maximum font file size that will be read */ private static long MAX_FILE_SIZE = 2000000; /** The user's home directory for locating fonts */ private static String userhome = System.getProperty("user.home"); /** The windows list of possible font locations */ private static File[] win32 = new File[] { new File("c:/windows/fonts") }; /** The macos list of possible font locations */ private static File[] macos = new File[] {new File("/System/Library/Fonts/"), new File("/Library/Fonts/"), new File("/System Folder/Fonts/"), new File("/Network/Library/Fonts/"), new File(userhome+"/Library/Fonts") }; /** The linux list of possible font locations */ private static File[] linux = new File[] {new File("/usr/share/fonts"), new File("/usr/share/X11/fonts") }; /** True if we're displaying debug en-route */ private static boolean DEBUG = true; /** The list of family names */ private static ArrayList families = new ArrayList(); /** The list of all the fonts available */ private static ArrayList fonts; /** Plain fonts */ private static HashMap plain = new HashMap(); /** Bold fonts */ private static HashMap bold = new HashMap(); /** Italic fonts */ private static HashMap italic = new HashMap(); /** Bold Italic fonts */ private static HashMap bolditalic = new HashMap(); /** Set the status listener */ private static StatusListener statusListener; /** The list of processed directories */ private static ArrayList processed = new ArrayList(); /** * Set the the status listener that will be notified of font loading progress * * @param listener The listener to be notified of font loading progress */ public static void setStatusListener(StatusListener listener) { statusListener = listener; } /** * Get the list of all the font family names available * * @return The list of family names available */ public static String[] getFamilyNames() { if (fonts == null) { getAllFonts(); } return (String[]) families.toArray(new String[0]); } /** * Get the plain version of a family name * * @param familyName The font family to retrieve * @return The plain version of the font or null if no plain version exits */ public static FontData getPlain(String familyName) { FontData data = (FontData) plain.get(familyName); return data; } /** * Get the bold version of the font * * @param familyName The name of the font family * @return The bold version of the font or null if no bold version exists */ public static FontData getBold(String familyName) { FontData data = (FontData) bold.get(familyName); return data; } /** * Get the bold italic version of the font * * @param familyName The name of the font family * @return The bold italic version of the font or null if no bold italic version exists */ public static FontData getBoldItalic(String familyName) { FontData data = (FontData) bolditalic.get(familyName); return data; } /** * Get the italic version of the font * * @param familyName The name of the font family * @return The italic version of the font or null if no italic version exists */ public static FontData getItalic(String familyName) { FontData data = (FontData) italic.get(familyName); return data; } /** * Get a styled version of a particular font family * * @param familyName The name of the font family * @param style The style (@see java.awt.Font#PLAIN) * @return The styled font or null if no such font exists */ public static FontData getStyled(String familyName, int style) { boolean b = (style & Font.BOLD) != 0; boolean i = (style & Font.ITALIC) != 0; if (b & i) { return getBoldItalic(familyName); } else if (b) { return getBold(familyName); } else if (i) { return getItalic(familyName); } else { return getPlain(familyName); } } /** * Process a directory potentially full of fonts * * @param dir The directory of fonts to process * @param fonts The fonts list to add to */ private static void processFontDirectory(File dir, ArrayList fonts) { if (!dir.exists()) { return; } if (processed.contains(dir)) { return; } processed.add(dir); File[] sources = dir.listFiles(); if (sources == null) { return; } for (int j=0;j<sources.length;j++) { File source = sources[j]; if (source.getName().equals(".")) { continue; } if (source.getName().equals("..")) { continue; } if (source.isDirectory()) { processFontDirectory(source, fonts); continue; } if (source.getName().toLowerCase().endsWith(".ttf")) { try { if (statusListener != null) { statusListener.updateStatus("Processing "+source.getName()); } FontData data = new FontData(new FileInputStream(source), 1); fonts.add(data); String famName = data.getFamilyName(); if (!families.contains(famName)) { families.add(famName); } boolean bo = data.getJavaFont().isBold(); boolean it = data.getJavaFont().isItalic(); if ((bo) && (it)) { bolditalic.put(famName, data); } else if (bo) { bold.put(famName, data); } else if (it) { italic.put(famName, data); } else { plain.put(famName, data); } } catch (Exception e) { if (DEBUG) { System.err.println("Unable to process: "+source.getAbsolutePath()+" ("+e.getClass()+": "+e.getMessage()+")"); } if (statusListener != null) { statusListener.updateStatus("Unable to process: "+source.getName()); } } } } } /** * Get all the fonts available * * @return The list of fonts available */ public static FontData[] getAllFonts() { if (fonts == null) { fonts = new ArrayList(); String os = System.getProperty("os.name"); File[] locs = new File[0]; if (os.startsWith("Windows")) { locs = win32; } if (os.startsWith("Linux")) { locs = linux; } if (os.startsWith("Mac OS")) { locs = macos; } for (int i=0;i<locs.length;i++) { File loc = locs[i]; processFontDirectory(loc, fonts); } if (os.startsWith("Linux")) { locateLinuxFonts(new File("/etc/fonts/fonts.conf")); } } return (FontData[]) fonts.toArray(new FontData[0]); } /** * Locate the linux fonts based on the XML configuration file * * @param file The location of the XML file */ private static void locateLinuxFonts(File file) { if (!file.exists()) { System.err.println("Unable to open: "+file.getAbsolutePath()); return; } try { InputStream in = new FileInputStream(file); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); ByteArrayOutputStream temp = new ByteArrayOutputStream(); PrintStream pout = new PrintStream(temp); while (reader.ready()) { String line = reader.readLine(); if (line.indexOf("DOCTYPE") == -1) { pout.println(line); } } in = new ByteArrayInputStream(temp.toByteArray()); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse(in); NodeList dirs = document.getElementsByTagName("dir"); for (int i=0;i<dirs.getLength();i++) { Element element = (Element) dirs.item(i); String dir = element.getFirstChild().getNodeValue(); if (dir.startsWith("~")) { dir = dir.substring(1); dir = userhome + dir; } addFontDirectory(new File(dir)); } NodeList includes = document.getElementsByTagName("include"); for (int i=0;i<includes.getLength();i++) { Element element = (Element) dirs.item(i); String inc = element.getFirstChild().getNodeValue(); if (inc.startsWith("~")) { inc = inc.substring(1); inc = userhome + inc; } locateLinuxFonts(new File(inc)); } } catch (Exception e) { e.printStackTrace(); System.err.println("Unable to process: "+file.getAbsolutePath()); } } /** * Add a font directory * * @param dir The directory containing fonts */ public static void addFontDirectory(File dir) { processFontDirectory(dir, fonts); } /** The java font version of the font */ private Font javaFont; /** The size of this instance of the font */ private float size; /** The UPEM */ private int upem; /** The mapping table from TTF */ private Map ansiKerning; /** The width of the characters */ private int[] charWidth; /** The name of the font */ private String fontName; /** The name of the font family */ private String familyName; /** * Create a new font data element * * @param ttf The TTF file to read * @param size The size of the new font * @throws IOException Indicates a failure to */ private FontData(InputStream ttf, float size) throws IOException { if (ttf.available() > MAX_FILE_SIZE) { throw new IOException("Can't load font - too big"); } byte[] data = IOUtils.toByteArray(ttf); if (data.length > MAX_FILE_SIZE) { throw new IOException("Can't load font - too big"); } this.size = size; try { javaFont = Font.createFont(Font.TRUETYPE_FONT, new ByteArrayInputStream(data)); TTFFile rawFont = new TTFFile(); if (!rawFont.readFont(new FontFileReader(data))) { throw new IOException("Invalid font file"); } upem = rawFont.getUPEM(); ansiKerning = rawFont.getAnsiKerning(); charWidth = rawFont.getAnsiWidth(); fontName = rawFont.getPostScriptName(); familyName = rawFont.getFamilyName(); String name = getName(); System.err.println("Loaded: "+name+" ("+data.length+")"); boolean bo = false; boolean it = false; if (name.indexOf(',') >= 0) { name = name.substring(name.indexOf(',')); if (name.indexOf("Bold") >= 0) { bo = true; } if (name.indexOf("Italic") >= 0) { it = true; } } if ((bo & it)) { javaFont = javaFont.deriveFont(Font.BOLD | Font.ITALIC); } else if (bo) { javaFont = javaFont.deriveFont(Font.BOLD); } else if (it) { javaFont = javaFont.deriveFont(Font.ITALIC); } } catch (FontFormatException e) { IOException x = new IOException("Failed to read font"); x.initCause(e); throw x; } } /** * Private default constructor for derivation */ private FontData() { } /** * Derive a new version of this font based on a new size * * @param size The size of the new font * @return The new font data */ public FontData deriveFont(float size) { return deriveFont(size, javaFont.getStyle()); } /** * Derive a new version of this font based on a new size * * @param size The size of the new font * @param style The style of the new font * @return The new font data */ public FontData deriveFont(float size, int style) { FontData original = getStyled(getFamilyName(), style); FontData data = new FontData(); data.size = size; data.javaFont = original.javaFont.deriveFont(style, size); data.upem = upem; data.ansiKerning = ansiKerning; data.charWidth = charWidth; return data; } /** * Get the full name of this font * * @return The full name of this font */ public String getName() { return fontName; } /** * Get the family name of this font * * @return The family name of this font */ public String getFamilyName() { return familyName; } /** * Get the size of this instance of the font data * * @return The size of the font */ public float getSize() { return size; } /** * Get the Java font representing this font data * * @return The Java font representing this font data */ public Font getJavaFont() { return javaFont; } /** * Get the kerning value between two characters * * @param first The first character * @param second The second character * @return The amount of kerning to apply between the two characters */ public int getKerning(char first, char second) { Map toMap = (Map) ansiKerning.get(new Integer(first)); if (toMap == null) { return 0; } Integer kerning = (Integer) toMap.get(new Integer(second)); if (kerning == null) { return 0; } return Math.round(convertUnitToEm(size, kerning.intValue())); } /** * Covert a "units" value to a point value based on a given * point size. * * @param ptSize The point size of the font being rendered * @param units The units to be converted * @return The size in points */ public float convertUnitToEm(float ptSize, int units) { return (units * ptSize) / upem; } /** * Get the "advance" value for the given character * * @param c The character to get the advance for * @return The adavance value for the given character */ public float getAdvance(char c) { return Math.round(convertUnitToEm(size, charWidth[c])); } /** * @see java.lang.Object#toString() */ public String toString() { return "[Font Data face='"+getName()+"' size="+size+" bold="+javaFont.isBold()+" italic="+javaFont.isItalic()+"]"; } }