package com.openfarmanager.android.core; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.util.HashMap; /** * author: Vlad Namashko */ public class FontManager { /** * Enumerates all fonts on Android system and returns the HashMap with the font * absolute file name as key, and the font literal name (embedded into the font) as value. * * @return fontPath/fontName pairs. */ static public HashMap<String, String> enumerateFonts() { String[] fontDirs = {"/system/fonts", "/system/font", "/data/fonts"}; HashMap<String, String> fonts = new HashMap<String, String>(); TTFAnalyzer analyzer = new TTFAnalyzer(); for (String fontDir : fontDirs) { File dir = new File(fontDir); if (!dir.exists()) continue; File[] files = dir.listFiles(); if (files == null) continue; for (File file : files) { String fontName = analyzer.getTtfFontName(file.getAbsolutePath()); if (fontName != null) fonts.put(file.getAbsolutePath(), fontName); } } return fonts.isEmpty() ? null : fonts; } } class TTFAnalyzer { // This function parses the TTF file and returns the font name specified in the file /** * Parse TTF file and return the font name specified in the file. * * @param fontFilename path to font file * @return font name */ public String getTtfFontName(String fontFilename) { try { // Parses the TTF file format. // See http://developer.apple.com/fonts/ttrefman/rm06/Chap6.html mFile = new RandomAccessFile(fontFilename, "r"); // Read the version first int version = readDword(); // The version must be either 'true' (0x74727565) or 0x00010000 if (version != 0x74727565 && version != 0x00010000) return null; // The TTF file consist of several sections called "tables", and we need to know how many of them are there. int numTables = readWord(); // Skip the rest in the header readWord(); // skip searchRange readWord(); // skip entrySelector readWord(); // skip rangeShift // Now we can read the tables for (int i = 0; i < numTables; i++) { // Read the table entry int tag = readDword(); readDword(); // skip checksum int offset = readDword(); int length = readDword(); // Now here' the trick. 'name' field actually contains the textual string name. // So the 'name' string in characters equals to 0x6E616D65 if (tag == 0x6E616D65) { // Here's the name section. Read it completely into the allocated buffer byte[] table = new byte[length]; mFile.seek(offset); read(table); // This is also a table. See http://developer.apple.com/fonts/ttrefman/rm06/Chap6name.html // According to Table 36, the total number of table records is stored in the second word, at the offset 2. // Getting the count and string offset - remembering it's big endian. int count = getWord(table, 2); int stringOffset = getWord(table, 4); // Record starts from offset 6 for (int record = 0; record < count; record++) { // Table 37 tells us that each record is 6 words -> 12 bytes, and that the nameID is 4th word so its offset is 6. // We also need to account for the first 6 bytes of the header above (Table 36), so... int nameIdOffset = record * 12 + 6; int platformID = getWord(table, nameIdOffset); int nameIdValue = getWord(table, nameIdOffset + 6); // Table 42 lists the valid name Identifiers. We're interested in 4 but not in Unicode encoding (for simplicity). // The encoding is stored as PlatformID and we're interested in Mac encoding if (nameIdValue == 4 && platformID == 1) { // We need the string offset and length, which are the word 6 and 5 respectively int nameLength = getWord(table, nameIdOffset + 8); int nameOffset = getWord(table, nameIdOffset + 10); // The real name string offset is calculated by adding the stringOffset nameOffset = nameOffset + stringOffset; // Make sure it is inside the array if (nameOffset >= 0 && nameOffset + nameLength < table.length) return new String(table, nameOffset, nameLength); } } } } return null; } catch (FileNotFoundException e) { // Permissions? return null; } catch (IOException e) { // Most likely a corrupted font file return null; } } /** * Font file; must be seekable */ private RandomAccessFile mFile = null; // Helper I/O functions private int readByte() throws IOException { return mFile.read() & 0xFF; } private int readWord() throws IOException { int b1 = readByte(); int b2 = readByte(); return b1 << 8 | b2; } private int readDword() throws IOException { int b1 = readByte(); int b2 = readByte(); int b3 = readByte(); int b4 = readByte(); return b1 << 24 | b2 << 16 | b3 << 8 | b4; } private void read(byte[] array) throws IOException { if (mFile.read(array) != array.length) throw new IOException(); } // Helper private int getWord(byte[] array, int offset) { int b1 = array[offset] & 0xFF; int b2 = array[offset + 1] & 0xFF; return b1 << 8 | b2; } }