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)