# frozen_string_literal: true require_relative '../../reader' module TTFunk class Table class Glyf # Composite TrueType glyph. class Compound include Reader # Flags bit 0: arg1 and arg2 are words. ARG_1_AND_2_ARE_WORDS = 0x0001 # Flags bit 3: there is a simple scale for the component. WE_HAVE_A_SCALE = 0x0008 # Flags bit 5: at least one more glyph after this one. MORE_COMPONENTS = 0x0020 # Flags bit 6: the x direction will use a different scale from the # y direction. WE_HAVE_AN_X_AND_Y_SCALE = 0x0040 # Flags bit 7: there is a 2 by 2 transformation that will be used to # scale the component. WE_HAVE_A_TWO_BY_TWO = 0x0080 # Flags bit 8: following the last component are instructions for the # composite character. WE_HAVE_INSTRUCTIONS = 0x0100 # Glyph ID. # @return [Integer] attr_reader :id # Binary serialization of this glyph. # @return [String] attr_reader :raw # Number of contours in this glyph. # @return [Integer] attr_reader :number_of_contours # Minimum x for coordinate. # @return [Integer] attr_reader :x_min # Minimum y for coordinate. # @return [Integer] attr_reader :y_min # Maximum x for coordinate. # @return [Integer] attr_reader :x_max # Maximum y for coordinate. # @return [Integer] attr_reader :y_max # IDs of compound glyphs. attr_reader :glyph_ids # Component glyph. # # @!attribute [rw] flags # Component flag. # @return [Integer] # @!attribute [rw] glyph_index # Glyph index of component. # @return [Integer] # @!attribute [rw] arg1 # x-offset for component or point number. # @return [Integer] # @!attribute [rw] arg2 # y-offset for component or point number. # @return [Integer] # @!attribute [rw] transform # Transformation. # @return [] Component = Struct.new(:flags, :glyph_index, :arg1, :arg2, :transform) # @param id [Integer] glyph ID. # @param raw [String] def initialize(id, raw) @id = id @raw = raw io = StringIO.new(raw) @number_of_contours, @x_min, @y_min, @x_max, @y_max = io.read(10).unpack('n*').map { |i| BinUtils.twos_comp_to_int(i, bit_width: 16) } # Because TTFunk only cares about glyphs insofar as they (1) provide # a bounding box for each glyph, and (2) can be rewritten into a # font subset, we don't really care about the rest of the glyph data # except as a whole. Thus, we don't actually decompose the glyph # into it's parts--all we really care about are the locations within # the raw string where the component glyph ids are stored, so that # when we rewrite this glyph into a subset we can rewrite the # component glyph-ids so they are correct for the subset. @glyph_ids = [] @glyph_id_offsets = [] offset = 10 # 2 bytes for each of num-contours, min x/y, max x/y loop do flags, glyph_id = @raw[offset, 4].unpack('n*') @glyph_ids << glyph_id @glyph_id_offsets << (offset + 2) break if (flags & MORE_COMPONENTS).zero? offset += 4 offset += if (flags & ARG_1_AND_2_ARE_WORDS).zero? 2 else 4 end if flags & WE_HAVE_A_TWO_BY_TWO != 0 offset += 8 elsif flags & WE_HAVE_AN_X_AND_Y_SCALE != 0 offset += 4 elsif flags & WE_HAVE_A_SCALE != 0 offset += 2 end end end # Is this a composite glyph? # @return [true] def compound? true end # Recode glyph. # # @param mapping [Hash{Integer => Integer}] a hash mapping old glyph IDs # to new glyph IDs. # @return [String] def recode(mapping) result = raw.dup new_ids = glyph_ids.map { |id| mapping[id] } new_ids.zip(@glyph_id_offsets).each do |new_id, offset| result[offset, 2] = [new_id].pack('n') end result end end end end end