class Prawn::SVG::TTF
  SFNT_VERSION_STRINGS = ["\x00\x01\x00\x00", 'true', 'typ1'].freeze
  LANGUAGE_IDS = [0, 0x409].freeze # English, US English
  UTF_16BE_PLATFORM_IDS = [0, 3].freeze # Unicode, Microsoft

  attr_reader :family, :subfamily, :weight_class

  def initialize(filename)
    load_data_from_file(filename)
  end

  def self.read_weight_class(io, font_offset)
    io.seek(font_offset)
    offset_table = io.read(12)
    return unless offset_table&.length == 12 && SFNT_VERSION_STRINGS.include?(offset_table[0..3])

    table_count = (offset_table[4].ord * 256) + offset_table[5].ord
    tables = io.read(table_count * 16)
    return unless tables&.length == table_count * 16

    table_count.times do |index|
      start = index * 16
      next unless tables[start..start + 3] == 'OS/2'

      offset, = tables[start + 8..start + 15].unpack('NN')
      io.seek(offset)
      data = io.read(6)
      return data[4..5].unpack1('n') if data&.length == 6
    end

    nil
  end

  def self.read_name_table(io, font_offset)
    io.seek(font_offset)
    offset_table = io.read(12)
    return unless offset_table && offset_table.length == 12 && SFNT_VERSION_STRINGS.include?(offset_table[0..3])

    table_count = (offset_table[4].ord * 256) + offset_table[5].ord
    tables = io.read(table_count * 16)
    return unless tables && tables.length == table_count * 16

    offset, length = table_count.times do |index|
      start = index * 16
      break tables[start + 8..start + 15].unpack('NNN') if tables[start..start + 3] == 'name'
    end

    return unless length

    io.seek(offset)
    data = io.read(length)
    return unless data && data.length == length

    _, name_count, string_offset = data[0..5].unpack('nnn')

    names = {}
    name_count.times do |index|
      start = 6 + (index * 12)
      platform_id, _, language_id, name_id, length, offset = data[start..start + 11].unpack('nnnnnn')
      next unless offset
      next unless LANGUAGE_IDS.include?(language_id)
      next unless [1, 2, 16, 17].include?(name_id)

      offset += string_offset
      field = data[offset..offset + length - 1]
      next unless field && field.length == length

      names[name_id] =
        if UTF_16BE_PLATFORM_IDS.include?(platform_id)
          begin
            field.force_encoding(Encoding::UTF_16BE).encode(Encoding::UTF_8)
          rescue StandardError
            field
          end
        else
          field
        end
    end

    family = names[16] || names[1]
    subfamily = names[17] || names[2]
    [family, subfamily]
  end

  private

  def load_data_from_file(filename)
    File.open(filename, 'rb') do |f|
      result = self.class.read_name_table(f, 0)
      @family, @subfamily = result if result
      @weight_class = self.class.read_weight_class(f, 0)
    end
  rescue Errno::ENOENT # in case the file disappears between the scan and the load, we don't want to crash
  end
end
