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

- old
+ new

@@ -1,29 +1,51 @@ # frozen_string_literal: true module TTFunk class Table class Cff < TTFunk::Table + # CFF Charset class Charset < TTFunk::SubTable include Enumerable + # First glyph string. This is an implicit glyph present in all charsets. FIRST_GLYPH_STRING = '.notdef' + + # Format 0. ARRAY_FORMAT = 0 + + # Format 1. RANGE_FORMAT_8 = 1 + + # Format 2. RANGE_FORMAT_16 = 2 + # Predefined ISOAdobe charset ID. ISO_ADOBE_CHARSET_ID = 0 + + # Predefined Expert charset ID. EXPERT_CHARSET_ID = 1 + + # Predefined Expert Subset charset ID. EXPERT_SUBSET_CHARSET_ID = 2 + # Default charset ID. DEFAULT_CHARSET_ID = ISO_ADOBE_CHARSET_ID class << self + # Standard strings defined in the spec that do not need to be defined + # in the CFF. + # + # @return [TTFunk::OneBasedArray<String>] def standard_strings Charsets::STANDARD_STRINGS end + # Strings for charset ID. + # + # @param charset_id [Integer] + # @return [TTFunk::OneBasedArray<String>] def strings_for_charset_id(charset_id) case charset_id when ISO_ADOBE_CHARSET_ID Charsets::ISO_ADOBE when EXPERT_CHARSET_ID @@ -32,37 +54,81 @@ Charsets::EXPERT_SUBSET end end end - attr_reader :entries, :length - attr_reader :top_dict, :format, :count, :offset_or_id + # Encoded entries. + # @return [TTFunk::OneBasedArray<Integer>, Array<Range<Integer>>] + attr_reader :entries + # Length of encoded charset subtable. + # @return [Integer] + attr_reader :length + + # 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 charset 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 charset_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_CHARSET_ID if offset super(file, offset, length) else - @count = self.class.strings_for_charset_id(offset_or_id).size + @items_count = self.class.strings_for_charset_id(offset_or_id).size end end + # Iterate over character names. + # + # @overload each() + # @yieldparam name [String] + # @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 name for glyph index. + # + # @param glyph_id [Integer] + # @return [String, nil] def [](glyph_id) return FIRST_GLYPH_STRING if glyph_id.zero? find_string(sid_for(glyph_id)) end + # Charset offset in the file. + # + # @return [Integer, nil] def offset # Numbers from 0..2 mean charset IDs instead of offsets. IDs are # basically pre-defined sets of characters. # # In the case of an offset, add the CFF table's offset since the @@ -71,17 +137,29 @@ if offset_or_id > 2 offset_or_id + top_dict.cff_offset end end - # mapping is new -> old glyph ids - def encode(mapping) + # Encode charset. + # + # @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) # no offset means no charset was specified (i.e. we're supposed to # use a predefined charset) so there's nothing to encode return '' unless offset - sids = mapping.keys.sort.map { |new_gid| sid_for(mapping[new_gid]) } + sids = + charmap + .values + .reject { |mapping| mapping[:new].zero? } + .sort_by { |mapping| mapping[:new] } + .map { |mapping| sid_for(mapping[:old]) } + ranges = TTFunk::BinUtils.rangify(sids) range_max = ranges.map(&:last).max range_bytes = if range_max.positive? @@ -136,11 +214,11 @@ if offset return self.class.standard_strings[sid] if sid <= 390 idx = sid - 390 - if idx < file.cff.string_index.count + if idx < file.cff.string_index.items_count file.cff.string_index[idx] end else self.class.strings_for_charset_id(offset_or_id)[sid] end @@ -151,44 +229,44 @@ @format = read(1, 'C').first case format_sym when :array_format - @count = top_dict.charstrings_index.count - 1 - @length = count * element_width + @items_count = top_dict.charstrings_index.items_count - 1 + @length = @items_count * element_width @entries = OneBasedArray.new(read(length, 'n*')) when :range_format8, :range_format16 # The number of ranges is not explicitly specified in the font. # Instead, software utilizing this data simply processes ranges # until all glyphs in the font are covered. - @count = 0 + @items_count = 0 @entries = [] @length = 0 - until count >= top_dict.charstrings_index.count - 1 + until @items_count >= top_dict.charstrings_index.items_count - 1 @length += 1 + element_width sid, num_left = read(element_width, element_format) - entries << (sid..(sid + num_left)) - @count += num_left + 1 + @entries << (sid..(sid + num_left)) + @items_count += num_left + 1 end end end def element_width(fmt = format_sym) { array_format: 2, # SID range_format8: 3, # SID + Card8 - range_format16: 4 # SID + Card16 + range_format16: 4, # SID + Card16 }[fmt] end def element_format(fmt = format_sym) { array_format: 'n', range_format8: 'nC', - range_format16: 'nn' + range_format16: 'nn', }[fmt] end def format_sym case @format @@ -202,10 +280,10 @@ def format_int(sym = format_sym) { array_format: ARRAY_FORMAT, range_format8: RANGE_FORMAT_8, - range_format16: RANGE_FORMAT_16 + range_format16: RANGE_FORMAT_16, }[sym] end end end end