lib/ttfunk/table/cff/encoding.rb in ttfunk-1.7.0 vs lib/ttfunk/table/cff/encoding.rb in ttfunk-1.8.0

- old
+ new

@@ -1,56 +1,104 @@ # frozen_string_literal: true module TTFunk class Table class Cff < TTFunk::Table + # CFF Encoding. class Encoding < TTFunk::SubTable include Enumerable + # Predefined Standard Encoding ID. STANDARD_ENCODING_ID = 0 + + # Predefined Expert Encoding ID. EXPERT_ENCODING_ID = 1 + # Default encoding ID. DEFAULT_ENCODING_ID = STANDARD_ENCODING_ID class << self + # Get predefined encoding by ID. + # + # @param encoding_id [Integer] + # @return [TTFunk::OneBasedArray<Integer>] def codes_for_encoding_id(encoding_id) case encoding_id when STANDARD_ENCODING_ID Encodings::STANDARD when EXPERT_ENCODING_ID Encodings::EXPERT end end end - attr_reader :top_dict, :format, :count, :offset_or_id + # Top dict. + # @return [TTFunk::Table::Cff::TopDict] + attr_reader :top_dict + # Encodign format. + # @return [Integer] + attr_reader :format + + # Number of encoded items. + # @return [Integer] + attr_reader :items_count + + # Offset or encoding ID. + # @return [Integer] + attr_reader :offset_or_id + + # @overload initialize(top_dict, file, offset = nil, length = nil) + # @param top_dict [TTFunk::Table:Cff::TopDict] + # @param file [TTFunk::File] + # @param offset [Integer] + # @param length [Integer] + # @overload initialize(top_dict, file, charset_id) + # @param top_dict [TTFunk::Table:Cff::TopDict] + # @param file [TTFunk::File] + # @param encoding_id [Integer] 0, 1, or 2 def initialize(top_dict, file, offset_or_id = nil, length = nil) @top_dict = top_dict @offset_or_id = offset_or_id || DEFAULT_ENCODING_ID if offset super(file, offset, length) + @supplemental = format >> 7 == 1 else - @count = self.class.codes_for_encoding_id(offset_or_id).size + @items_count = self.class.codes_for_encoding_id(offset_or_id).size + @supplemental = false end end + # Iterate over character codes. + # + # @overload each() + # @yieldparam code [Integer] + # @return [void] + # @overload each() + # @return [Enumerator] def each return to_enum(__method__) unless block_given? # +1 adjusts for the implicit .notdef glyph - (count + 1).times { |i| yield self[i] } + (items_count + 1).times { |i| yield(self[i]) } end + # Get character code for glyph index. + # + # @param glyph_id [Integer] + # @return [Integer, nil] def [](glyph_id) return 0 if glyph_id.zero? return code_for(glyph_id) if offset self.class.codes_for_encoding_id(offset_or_id)[glyph_id] end + # Encoding offset in the file. + # + # @return [Integer, nil] def offset # Numbers from 0..1 mean encoding IDs instead of offsets. IDs are # pre-defined, generic encodings that define the characters present # in the font. # @@ -60,20 +108,29 @@ if offset_or_id > 1 offset_or_id + top_dict.cff_offset end end - def encode(new_to_old, old_to_new) - # no offset means no encoding was specified (i.e. we're supposed to - # use a predefined encoding) so there's nothing to encode - return '' unless offset - return encode_supplemental(new_to_old, old_to_new) if supplemental? + # Encode encoding. + # + # @param charmap [Hash{Integer => Hash}] keys are the charac codes, + # values are hashes: + # * `:old` (<tt>Integer</tt>) - glyph ID in the original font. + # * `:new` (<tt>Integer</tt>) - glyph ID in the subset font. + # @return [String] + def encode(charmap) + # Any subset encoding is all but guaranteed to be different from the + # standard encoding so we don't even attempt to see if it matches. We + # assume it's different and just encode it anew. + return encode_supplemental(charmap) if supplemental? + codes = - new_to_old.keys.sort.map do |new_gid| - code_for(new_to_old[new_gid]) - end + charmap + .reject { |_code, mapping| mapping[:new].zero? } + .sort_by { |_code, mapping| mapping[:new] } + .map { |(code, _m)| code } ranges = TTFunk::BinUtils.rangify(codes) # calculate whether storing the charset as a series of ranges is # more efficient (i.e. takes up less space) vs storing it as an @@ -91,24 +148,25 @@ ranges.each { |range| result << range.pack(element_fmt) } result end end + # Is this a supplemental encoding? + # + # @return [Boolean] def supplemental? # high-order bit set to 1 indicates supplemental encoding - @format >> 7 == 1 + @supplemental end private - def encode_supplemental(_new_to_old, old_to_new) + def encode_supplemental(charmap) new_entries = - @entries.each_with_object({}) do |(code, old_gid), ret| - if (new_gid = old_to_new[old_gid]) - ret[code] = new_gid - end - end + charmap + .reject { |_code, mapping| mapping[:new].zero? } + .transform_values { |mapping| mapping[:new] } result = [format_int(:supplemental), new_entries.size].pack('CC') fmt = element_format(:supplemental) new_entries.each do |code, new_gid| @@ -148,26 +206,26 @@ @format, entry_count = read(2, 'C*') @length = entry_count * element_width case format_sym when :array_format - @count = entry_count + @items_count = entry_count @entries = OneBasedArray.new(read(length, 'C*')) when :range_format @entries = [] - @count = 0 + @items_count = 0 entry_count.times do code, num_left = read(element_width, element_format) @entries << (code..(code + num_left)) - @count += num_left + 1 + @items_count += num_left + 1 end when :supplemental @entries = {} - @count = entry_count + @items_count = entry_count entry_count.times do code, glyph = read(element_width, element_format) @entries[code] = glyph end @@ -176,10 +234,10 @@ def element_format(fmt = format_sym) { array_format: 'C', range_format: 'CC', - supplemental: 'Cn' + supplemental: 'Cn', }[fmt] end # @TODO: handle supplemental encoding (necessary?) def element_width(fmt = format_sym)