Home » Android » Android — retrieve default system font string name

Android — retrieve default system font string name

Posted by: admin May 14, 2020 Leave a comment

Questions:

With some flavors of Android (Samsung, LG, HTC), it looks like it’s possible to set the default system font without rooting. I don’t have one of these devices to test out on (I’ve got an older Lenovo tablet), but I’m wondering the following:

  1. Does the Typeface.DEFAULT method return the Typeface of the custom font on these devices, or always return info on Roboto?
  2. If it does return info on the custom Typeface, how can I programmatically get the typeface name (string) of the custom Typeface?
  3. If Typeface.DEFAULT is a dead end, is there another way to get at the default System font name? Poking around for TTF files, maybe?

EDIT: I’m adding a way that I thought might work, but doesn’t in reality: comparing Typeface.DEFAULT to Typeface objects created from files in the /system/fonts, /system/font, and /data/fonts directories. This is not an answer, but it might help spur someone on to come up with one. Also note that the TTFAnalyzer class is not mine. Code follows:

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: some more information from poking around on my Lenovo tablet. In /system/etc, there are a couple xml files of interest:

  • system_fonts.xml — which looks like it has the default fonts for regular / italic / bold as its first family entry
  • fallback_fonts.xml — which has the fonts Android is supposed to fall back on if it can’t find the glyph in the current font (for example, Thai characters).

It might be worth parsing through that system_fonts and returning the default font name from there — but I’ve no idea if that’s the “correct” way to do this.

How to&Answers:

Okay, here’s something that I think works. As to whether or not it’s Android approved, well, It’s kinda hacky. This solution assumes a few things:

  • The file /system/etc/system_fonts.xml contains the list of fonts on the device.
  • The first <file> element in that system_fonts.xml file is the default font for the device.
  • The default font is located in /system/fonts.

These assumptions are from looking through the Typeface class file from Android 5.x, but I’ve tested on a couple other versions and the solution seems to work there as well. The code follows (assuming TTFAnalyzer class, listed above):

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