From http://www.jwz.org/xscreensaver/xscreensaver-5.35.tar.gz
[xscreensaver] / android / project / xscreensaver / src / org / jwz / xscreensaver / TTFAnalyzer.java
1 /* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2
3  * Copyright (C) 2011 George Yunaev @ Ulduzsoft
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6
7   http://www.ulduzsoft.com/2012/01/enumerating-the-fonts-on-android-platform/
8  */
9  
10 package org.jwz.xscreensaver;
11
12 import java.io.File;
13 import java.io.FileNotFoundException;
14 import java.io.IOException;
15 import java.io.RandomAccessFile;
16 import java.util.HashMap;
17  
18 // The class which loads the TTF file, parses it and returns the TTF font name
19 class TTFAnalyzer
20 {
21     // This function parses the TTF file and returns the font name specified in the file
22     public String getTtfFontName( String fontFilename )
23     {
24         try
25         {
26             // Parses the TTF file format.
27             // See http://developer.apple.com/fonts/ttrefman/rm06/Chap6.html
28             m_file = new RandomAccessFile( fontFilename, "r" );
29  
30             // Read the version first
31             int version = readDword();
32  
33             // The version must be either 'true' (0x74727565) or 0x00010000 or 'OTTO' (0x4f54544f) for CFF style fonts.
34             if ( version != 0x74727565 && version != 0x00010000 && version != 0x4f54544f)
35                 return null;
36  
37             // The TTF file consist of several sections called "tables", and we need to know how many of them are there.
38             int numTables = readWord();
39  
40             // Skip the rest in the header
41             readWord(); // skip searchRange
42             readWord(); // skip entrySelector
43             readWord(); // skip rangeShift
44  
45             // Now we can read the tables
46             for ( int i = 0; i < numTables; i++ )
47             {
48                 // Read the table entry
49                 int tag = readDword();
50                 readDword(); // skip checksum
51                 int offset = readDword();
52                 int length = readDword();
53  
54                 // Now here' the trick. 'name' field actually contains the textual string name.
55                 // So the 'name' string in characters equals to 0x6E616D65
56                 if ( tag == 0x6E616D65 )
57                 {
58                     // Here's the name section. Read it completely into the allocated buffer
59                     byte[] table = new byte[ length ];
60  
61                     m_file.seek( offset );
62                     read( table );
63  
64                     // This is also a table. See http://developer.apple.com/fonts/ttrefman/rm06/Chap6name.html
65                     // According to Table 36, the total number of table records is stored in the second word, at the offset 2.
66                     // Getting the count and string offset - remembering it's big endian.
67                     int count = getWord( table, 2 );
68                     int string_offset = getWord( table, 4 );
69  
70                     // Record starts from offset 6
71                     for ( int record = 0; record < count; record++ )
72                     {
73                         // Table 37 tells us that each record is 6 words -> 12 bytes, and that the nameID is 4th word so its offset is 6.
74                         // We also need to account for the first 6 bytes of the header above (Table 36), so...
75                         int nameid_offset = record * 12 + 6;
76                         int platformID = getWord( table, nameid_offset );
77                         int nameid_value = getWord( table, nameid_offset + 6 );
78  
79                         // Table 42 lists the valid name Identifiers. We're interested in 4 but not in Unicode encoding (for simplicity).
80                         // The encoding is stored as PlatformID and we're interested in Mac encoding
81                         if ( nameid_value == 4 && platformID == 1 )
82                         {
83                             // We need the string offset and length, which are the word 6 and 5 respectively
84                             int name_length = getWord( table, nameid_offset + 8 );
85                             int name_offset = getWord( table, nameid_offset + 10 );
86  
87                             // The real name string offset is calculated by adding the string_offset
88                             name_offset = name_offset + string_offset;
89  
90                             // Make sure it is inside the array
91                             if ( name_offset >= 0 && name_offset + name_length < table.length )
92                                 return new String( table, name_offset, name_length );
93                         }
94                     }
95                 }
96             }
97  
98             return null;
99         }
100         catch (FileNotFoundException e)
101         {
102             // Permissions?
103             return null;
104         }
105         catch (IOException e)
106         {
107             // Most likely a corrupted font file
108             return null;
109         }
110     }
111  
112     // Font file; must be seekable
113     private RandomAccessFile m_file = null;
114  
115     // Helper I/O functions
116     private int readByte() throws IOException
117     {
118         return m_file.read() & 0xFF;
119     }
120  
121     private int readWord() throws IOException
122     {
123         int b1 = readByte();
124         int b2 = readByte();
125  
126         return b1 << 8 | b2;
127     }
128  
129     private int readDword() throws IOException
130     {
131         int b1 = readByte();
132         int b2 = readByte();
133         int b3 = readByte();
134         int b4 = readByte();
135  
136         return b1 << 24 | b2 << 16 | b3 << 8 | b4;
137     }
138  
139     private void read( byte [] array ) throws IOException
140     {
141         if ( m_file.read( array ) != array.length )
142             throw new IOException();
143     }
144  
145     // Helper
146     private int getWord( byte [] array, int offset )
147     {
148         int b1 = array[ offset ] & 0xFF;
149         int b2 = array[ offset + 1 ] & 0xFF;
150  
151         return b1 << 8 | b2;
152     }
153 }