content: /*
   * Copyright (C) 2015-2023 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;
      }
  
  }