Enumerating the fonts on Android platform

Some Android applications may find it useful to obtain the full list of the fonts installed on the device together with the font names.They could be used to choose the best matching font for specific purposes, or to let the user choose the font. Unfortunately the Android platform provides no such API in Java.

It is still possible to enumerate all the fonts on the device using the file system operations. The fonts are stored in several directories, the /system/fonts being the most typical. However it would be nice to provide the font name as well, not just the file name. This could be achieved by parsing the TTF file format, which is quite a simple task.

This post provides the source code for the Android font enumerator which is used in the Karaoke player project. The method enumerateFonts() returns the HashMap of all the fonts installed on the device. Each entry in the map has the absolute path to the font as the key, and the font name as a value.

The code below is licensed under the Apache license.

/*
 * Copyright (C) 2011 George Yunaev @ Ulduzsoft
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 */

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.HashMap;

public class FontManager
{
	// This function 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.
	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;
	}
}

// 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;
	}
}
This entry was posted in android.

2 Responses to Enumerating the fonts on Android platform

  1. Jens-Heiner Rechtien says:

    This is very helpful! Thanks! And with a very small change FontManager can additionally enumerate OTF style fonts which are found on some Android systems, like the Samsung Galaxy 10.1.


    *** old/FontManager.java Thu May 3 18:05:36 2012
    --- new/FontManager.java Thu May 3 18:07:34 2012
    ***************
    *** 60,67 ****
    // 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.
    --- 60,67 ----
    // Read the version first
    int version = readDword();

    ! // The version must be either 'true' (0x74727565) or 0x00010000 or 'OTTO' (0x4f54544f) for CFF style fonts.
    ! if ( version != 0x74727565 && version != 0x00010000 && version != 0x4f54544f)
    return null;

    // The TTF file consist of several sections called "tables", and we need to know how many of them are there.

  2. shyam says:

    Very helpful.Can we get generic font family names from this data.Like serif /sans-serif