lib/ttfunk/table/post.rb



# frozen_string_literal: true

require_relative '../table'

module TTFunk
  class Table
    # PostScript (`post`) table.
    #
    # This class can be extended with version-specific modules.
    #
    # @see TTFunk::Table::Post::Format10
    # @see TTFunk::Table::Post::Format20
    # @see TTFunk::Table::Post::Format30
    # @see TTFunk::Table::Post::Format40
    class Post < Table
      # Table version.
      # @return [Integer]
      attr_reader :format

      # Italic angle in counter-clockwise degrees from the vertical.
      # @return [Integer]
      attr_reader :italic_angle

      # Suggested distance of the top of the underline from the baseline
      # @return [Integer]
      attr_reader :underline_position

      # Suggested values for the underline thickness.
      # @return [Integer]
      attr_reader :underline_thickness

      # 0 if the font is proportionally spaced, non-zero if the font is not
      # proportionally spaced.
      # @return [Integer]
      attr_reader :fixed_pitch

      # Minimum memory usage when an OpenType font is downloaded.
      # @return [Integer]
      attr_reader :min_mem_type42

      # Maximum memory usage when an OpenType font is downloaded.
      # @return [Integer]
      attr_reader :max_mem_type42

      # Minimum memory usage when an OpenType font is downloaded as
      # a Type 1 font.
      # @return [Integer]
      attr_reader :min_mem_type1

      # Maximum memory usage when an OpenType font is downloaded as
      # a Type 1 font.
      # @return [Integer]
      attr_reader :max_mem_type1

      # Version-specific fields.
      # @return [TTFunk::Table::Post::Format10, TTFunk::Table::Post::Format20,
      #   TTFunk::Table::Post::Format30, TTFunk::Table::Post::Format40]
      attr_reader :subtable

      # Encode table.
      #
      # @param post [TTFunk::Table::Post]
      # @param mapping [Hash{Integer => Integer}] keys are new glyph IDs, values
      #   are old glyph IDs
      # @return [String, nil]
      def self.encode(post, mapping)
        return if post.nil?

        post.recode(mapping)
      end

      # Is this font monospaced?
      #
      # @return [Boolean]
      def fixed_pitch?
        @fixed_pitch != 0
      end

      # Get glyph name for character code.
      #
      # This is a placeholder.
      #
      # @param _code [Integer]
      # @return [String]
      def glyph_for(_code)
        '.notdef'
      end

      # Re-encode this table.
      #
      # @param mapping [Hash{Integer => Integer}] keys are new glyph IDs, values
      #   are old glyph IDs
      # @return [String]
      def recode(mapping)
        return raw if format == 0x00030000

        table = raw[0, 32]
        table[0, 4] = [0x00020000].pack('N')

        index = []
        strings = []

        mapping.keys.sort.each do |new_id|
          post_glyph = glyph_for(mapping[new_id])
          position = Format10::POSTSCRIPT_GLYPHS.index(post_glyph)
          if position
            index << position
          else
            index << (257 + strings.length)
            strings << post_glyph
          end
        end

        table << [mapping.length, *index].pack('n*')
        strings.each do |string|
          table << [string.length, string].pack('CA*')
        end

        table
      end

      private

      def parse!
        @format, @italic_angle, @underline_position, @underline_thickness,
          @fixed_pitch, @min_mem_type42, @max_mem_type42,
          @min_mem_type1, @max_mem_type1 = read(32, 'N2n2N*')

        @subtable =
          case @format
          when 0x00010000
            extend(Post::Format10)
          when 0x00020000
            extend(Post::Format20)
          when 0x00025000
            raise NotImplementedError,
              'Post format 2.5 is not supported by TTFunk'
          when 0x00030000
            extend(Post::Format30)
          when 0x00040000
            extend(Post::Format40)
          end

        parse_format!
      end

      def parse_format!
        warn(Kernel.format('postscript table format 0x%08X is not supported', @format))
      end
    end
  end
end

require_relative 'post/format10'
require_relative 'post/format20'
require_relative 'post/format30'
require_relative 'post/format40'