2016-09-26 14 views
11

W przypadku niektórych smaków Androida (Samsung, LG, HTC) wygląda na to, że możliwe jest uzyskanie set the default system font without rooting. Nie mam jednego z tych urządzeń do testowania na (mam starszą tablet Lenovo), ale zastanawiam się, co następuje:Android - pobierz domyślną nazwę ciągu systemowego:

  1. Czy metoda Typeface.DEFAULT powrócić kroju zwyczaju czcionkę na tych urządzeniach lub zawsze zwracać informacje o Roboto?
  2. Jeśli zwraca informacje o niestandardowej czcionce, w jaki sposób mogę programowo uzyskać nazwę kroju pisma niestandardowego kroju pisma?
  3. Jeśli Typeface.DEFAULT jest ślepym zaułkiem, czy istnieje inny sposób uzyskania domyślnej nazwy czcionki systemowej? Może chodziło ci o pliki TTF?

EDIT: Dodaję sposób, że pomyślałem, że może działać, ale nie w rzeczywistości: Porównanie Typeface.DEFAULT do krojem obiekty utworzone z plików w /system/fonts, /system/font, a /data/fonts katalogów. To nie jest odpowiedź, ale może to zachęcić kogoś do wymyślenia jednej. Należy również pamiętać, że klasa TTFAnalyzer to , a nie moja. Kod następująco:

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; 
} 
} 

EDIT 2: trochę więcej informacji od wywiercenie na moim tablecie Lenovo. W /system/etc, istnieje kilka plików xml atrakcji:

  • system_fonts.xml - która wygląda jak to ma czcionek domyślnych regularne/kursywa/śmiałe jako swojego pierwszego wpisu rodzinnym
  • fallback_fonts.xml - który ma czcionek Android powinien się ponownie włączyć, jeśli nie może znaleźć glifu w aktualnej czcionce (na przykład znaki tajlandzkie).

Może warto przeanalizować system_fonts i zwrócić domyślną nazwę czcionki - ale nie mam pojęcia, czy to "poprawny" sposób na zrobienie tego.

+1

Patrząc na źródło 'Typeface' klasy, wygląda to tylko zalążek i kod każda metoda jest' rzucić nowe RuntimeException ('Stub!) '. Ponadto stałe, w tym "DEFAULT", są ustawione na "null". Dlatego "krój pisma".DEFAULT "powinno zawsze dawać' null'. Nie bardzo rozumiem, jak działają rzeczywiste połączenia z tą klasą. –

+1

Cześć Aleks - skąd czerpiesz źródło? Szukałem i mogę znaleźć https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/graphics/java/android/graphics/Typeface.java - która wydaje się być być kimś więcej niż stubem. Nie jestem pewien, czy jest to faktyczna baza źródłowa. : -/ – eb1

+0

Hm, ciekawe ... Spoglądałem na starszą wersję, ponieważ to właśnie miałem wokół - myślę, że było to 4,3, a może nawet 4,1 –

Odpowiedz

2

OK, oto coś, co moim zdaniem działa. Jeśli chodzi o to, czy jest to aprobata Androida, cóż, to trochę hacky. To rozwiązanie zakłada kilka rzeczy:

  • Plik /system/etc/system_fonts.xml zawiera listę czcionek na urządzeniu.
  • Pierwszy element <file> w tym pliku system_fonts.xml jest domyślną czcionką urządzenia. Domyślna czcionka znajduje się w /system/fonts.

Te założenia są z pominie pliku Typeface klasy z Android 5.x, ale Przetestowałem na kilku innych wersjach, a rozwiązanie wydaje się nie działa tak dobrze. Kod następująco (zakładając klasę TTFAnalyzer, uszeregowane powyżej):

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; 
}