/* * Copyright (C) 2015-2017 Emanuel Moecklin * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.onegravity.rteditor.fonts; import android.content.res.AssetManager; import com.onegravity.rteditor.utils.io.IOUtils; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; /** * This class offers methods to analyze ttf files, namely to retrieve the font name. */ abstract class TTFAnalyzer { /** * Retrieve the file name for a system font. * * @param filePath the full path for the font to retrieve. * * @return The file name or null of none could be retrieved. */ static String getFontName(String filePath) { TTFRandomAccessFile in = null; try { RandomAccessFile file = new RandomAccessFile(filePath, "r"); in = new TTFRandomAccessFile(file); return getTTFFontName(in, filePath); } catch (IOException e) { return null; // Missing permissions or corrupted font file? } finally { IOUtils.closeQuietly(in); } } /** * Retrieve the file name for a font in the asset folder. * * @param filePath the full path for the font in the asset folder to retrieve. * * @return The file name or null of none could be retrieved. */ static String getFontName(AssetManager assets, String filePath) { TTFAssetInputStream in = null; try { InputStream file = assets.open(filePath, AssetManager.ACCESS_RANDOM); in = new TTFAssetInputStream(file); return getTTFFontName(in, filePath); } catch (FileNotFoundException e) { return null; // Missing permissions? } catch (IOException e) { return null; // Corrupted font file? } finally { IOUtils.closeQuietly(in); } } private static String getTTFFontName(TTFInputStream in, String fontFilename) { try { // Read the version first int version = readDword(in); // 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. int numTables = readWord(in); // Skip the rest in the header readWord(in); // skip searchRange readWord(in); // skip entrySelector readWord(in); // skip rangeShift // Now we can read the tables for (int i = 0; i < numTables; i++) { // Read the table entry int tag = readDword(in); readDword(in); // skip checksum int offset = readDword(in); int length = readDword(in); // 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]; in.seek(offset); read(in, 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; } } private static int readByte(TTFInputStream in) throws IOException { return in.read() & 0xFF; } private static int readWord(TTFInputStream in) throws IOException { int b1 = readByte(in); int b2 = readByte(in); return b1 << 8 | b2; } private static int readDword(TTFInputStream in) throws IOException { int b1 = readByte(in); int b2 = readByte(in); int b3 = readByte(in); int b4 = readByte(in); return b1 << 24 | b2 << 16 | b3 << 8 | b4; } private static void read(TTFInputStream in, byte[] array) throws IOException { if (in.read(array) != array.length) throw new IOException(); } private static int getWord(byte[] array, int offset) { int b1 = array[offset] & 0xFF; int b2 = array[offset + 1] & 0xFF; return b1 << 8 | b2; } }