2016-09-26 97 views
11

隨着Android(三星,LG,HTC)的一些口味,它看起來像是可能的set the default system font without rooting。我沒有這些設備來測試對一個(我有一個較舊的聯想平板電腦),但我想知道以下幾點:Android - 檢索默認系統字體字符串名稱

  1. 是否Typeface.DEFAULT方法返回定製的字樣這些設備上的字體還是始終返回Roboto的信息?
  2. 如果它確實返回自定義字體上的信息,如何以編程方式獲取自定義字體的字體名稱(字符串)?
  3. 如果Typeface.DEFAULT是一個死衚衕,有沒有另一種方法來獲取默認的系統字體名稱?也許是爲了TTF文件?

編輯:我添加的方式,我想可能會奏效,但在現實中並不:比較Typeface.DEFAULT到字體在/system/fonts/system/font,並/data/fonts目錄從文件中創建的對象。 這不是一個答案,但它可能有助於激勵某人想出一個答案。另請注意,TTFAnalyzer類是而不是地雷。代碼如下:

private String getDefaultFont() { 
    String[] fontdirs = { "/system/fonts", "/system/font", "/data/fonts" }; 
    TTFAnalyzer analyzer = new TTFAnalyzer(); 
    Typeface tfDefault = Typeface.DEFAULT; 
    Typeface tfTemp = null; 
    String defaultFontName = ""; 

    System.out.println("getDefaultFont(): entry"); 
    System.out.println("tfDefault: " + tfDefault.toString()); 

    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) { 
       System.out.println("found font: " + fontname); 
       tfTemp = Typeface.createFromFile(file); 
       System.out.println("tfTemp: " + tfTemp.toString()); 
       //** THIS SHOULD BE WORKING? **// 
       if (tfTemp.equals(tfDefault)) { 
        System.out.println("Found default font: " + fontname); 
        defaultFontName = fontname; 
       } 
      } 
     } 
    } 
    return defaultFontName; 
} 

// The class which loads the TTF file, parses it and returns the TTF font name 
class TTFAnalyzer 
{ 
// This function parses the TTF file and returns the font name specified in the file 
public String getTtfFontName(String fontFilename) 
{ 
    try 
    { 
     // Parses the TTF file format. 
     // See http://developer.apple.com/fonts/ttrefman/rm06/Chap6.html 
     m_file = 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 ]; 

       m_file.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 string_offset = 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 nameid_offset = record * 12 + 6; 
        int platformID = getWord(table, nameid_offset); 
        int nameid_value = getWord(table, nameid_offset + 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 (nameid_value == 4 && platformID == 1) 
        { 
         // We need the string offset and length, which are the word 6 and 5 respectively 
         int name_length = getWord(table, nameid_offset + 8); 
         int name_offset = getWord(table, nameid_offset + 10); 

         // The real name string offset is calculated by adding the string_offset 
         name_offset = name_offset + string_offset; 

         // Make sure it is inside the array 
         if (name_offset >= 0 && name_offset + name_length < table.length) 
          return new String(table, name_offset, name_length); 
        } 
       } 
      } 
     } 

     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 m_file = null; 

// Helper I/O functions 
private int readByte() throws IOException 
{ 
    return m_file.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 (m_file.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; 
} 
} 

編輯2:從我的聯想平板打交道了一些更多的信息。在/system/etc,有感興趣的一對夫婦的XML文件:

  • system_fonts.xml - 這看起來有定期的默認字體/斜體/大膽作爲其第一家進入
  • fallback_fonts.xml - 其中有字體如果Android無法在當前字體中找到字形(例如,泰文字符),則應該使用Android。

這可能是值得解析通過該system_fonts並從那裏返回默認的字體名稱 - 但我不知道如果這是「正確」的方式來做到這一點。

+1

望着Typeface'類的'源,它看起來像這只是一個存根,每個方法的代碼'拋出新的RuntimeException(「存根!」 )'。此外,常量(包括'DEFAULT')被設置爲'null'。因此'字體。DEFAULT'應該總是給你'空'。我不太明白這個班上的實際電話是如何工作的。 –

+1

嗨阿列克斯 - 你從哪裏得到來源?我搜索了周圍,並可以找到https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/graphics/java/android/graphics/Typeface.java - 這似乎確實不僅僅是一個存根。不過,我不確定這是否是實際的源代碼庫。 : -/ – eb1

+0

嗯,有趣......我在看一個較舊的版本,因爲這就是我所處的位置 - 我認爲它是4.3或甚至4.1 –

回答

2

好的,這是我認爲有效的東西。至於它是否通過了Android的批准,那麼,這是有點hacky。此解決方案假設了幾件事情:

  • 文件/system/etc/system_fonts.xml包含設備上的字體列表。
  • 那個system_fonts.xml文件中的第一個<file>元素是設備的默認字體。
  • 默認字體位於/system/fonts

這些假設是從Android 5.x中查看Typeface類文件,但我測試了其他幾個版本,並且該解決方案似乎也適用於此。代碼如下(假設TTFAnalyzer類,上面列出):

import android.util.Xml; 

import org.xmlpull.v1.XmlPullParser; 
import org.xmlpull.v1.XmlPullParserException; 

public String getDefaultFont() { 
    System.out.println("getFontList(): entry"); 
    File configFilename = new File("/system/etc/system_fonts.xml"); 
    String defaultFontName = ""; 
    TTFAnalyzer analyzer = new TTFAnalyzer(); 

    try { 
     FileInputStream fontsIn = new FileInputStream(configFilename); 
     XmlPullParser parser = Xml.newPullParser(); 
     parser.setInput(fontsIn, null); 
     Boolean done = false; 
     Boolean getTheText = false; 
     int eventType; 
     String defaultFont = ""; 
     while (!done) { 
      eventType = parser.next(); 
      if (eventType == parser.START_TAG && parser.getName().equalsIgnoreCase("file")) { 
       // the text is next up -- pull it 
       getTheText = true; 
      } 
      if (eventType == parser.TEXT && getTheText == true) { 
       // first name 
       defaultFont = parser.getText(); 
       System.out.println("text for file tag:" + defaultFont); 
       done = true; 
      } 
      if (eventType == parser.END_DOCUMENT) { 
       System.out.println("hit end of system_fonts.xml document"); 
       done = true; 
      } 
     } 

     if (defaultFont.length() > 0) { 
      // found the font filename, most likely in /system/fonts. Now pull out the human-readable 
      // string from the font file 
      System.out.println("Figuring out default Font info"); 
      String fontname = analyzer.getTtfFontName("/system/fonts/" + defaultFont); 
      if (fontname != null) { 
       System.out.println("found font info: " + fontname); 
       defaultFontName = fontname; 
      }     
     } 

    } catch (RuntimeException e) { 
     System.err.println("Didn't create default family (most likely, non-Minikin build)"); 
     // TODO: normal in non-Minikin case, remove or make error when Minikin-only 
    } catch (FileNotFoundException e) { 
     System.err.println("GetDefaultFont: config file Not found"); 
    } catch (IOException e) { 
     System.err.println("GetDefaultFont: IO exception: " + e.getMessage()); 
    } catch (XmlPullParserException e) { 
     System.err.println("getDefaultFont: XML parse exception " + e.getMessage()); 
    } 
    return defaultFontName; 
}