class TTFunk::Table::Cff::FdSelector

CFF FDSelect.

Constants

ARRAY_ENTRY_SIZE

Array entry size.

ARRAY_FORMAT

Array format.

RANGE_ENTRY_SIZE

Range entry size.

RANGE_FORMAT

Range format.

Attributes

entries[R]

Number of entries. @return [Array<Integer>] if format is array. @return [Array<Array(Range, Integer)>] if format is range.

items_count[R]

Number of encoded items. @return [Integer]

n_glyphs[R]

Number of glyphs. @return [Integer]

top_dict[R]

Top dict. @return [TTFunk::Table::Cff::TopDict]

Public Class Methods

new(top_dict, file, offset, length = nil) click to toggle source

@param top_dict [TTFunk::Table:Cff::TopDict] @param file [TTFunk::File] @param offset [Integer] @param length [Integer]

Calls superclass method TTFunk::SubTable::new
# File lib/ttfunk/table/cff/fd_selector.rb, line 43
def initialize(top_dict, file, offset, length = nil)
  @top_dict = top_dict
  super(file, offset, length)
end

Public Instance Methods

[](glyph_id) click to toggle source

Get font dict index for glyph ID.

@return [Integer]

# File lib/ttfunk/table/cff/fd_selector.rb, line 51
def [](glyph_id)
  case format_sym
  when :array_format
    entries[glyph_id]

  when :range_format
    if (entry = range_cache[glyph_id])
      return entry
    end

    range, entry =
      entries.bsearch { |rng, _|
        if rng.cover?(glyph_id)
          0
        elsif glyph_id < rng.first
          -1
        else
          1
        end
      }

    range.each { |i| range_cache[i] = entry }
    entry
  end
end
each() { |self| ... } click to toggle source

Iterate over font dicts for each glyph ID.

@yieldparam [Integer] font dict index. @return [void]

# File lib/ttfunk/table/cff/fd_selector.rb, line 81
def each
  return to_enum(__method__) unless block_given?

  items_count.times { |i| yield(self[i]) }
end
encode(charmap) click to toggle source

Encode Font dict selector.

@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]

# File lib/ttfunk/table/cff/fd_selector.rb, line 94
def encode(charmap)
  # get list of [new_gid, fd_index] pairs
  new_indices =
    charmap
      .reject { |code, mapping| mapping[:new].zero? && !code.zero? }
      .sort_by { |_code, mapping| mapping[:new] }
      .map { |(_code, mapping)| [mapping[:new], self[mapping[:old]]] }

  ranges = rangify_gids(new_indices)
  total_range_size = ranges.size * RANGE_ENTRY_SIZE
  total_array_size = new_indices.size * ARRAY_ENTRY_SIZE

  ''.b.tap do |result|
    if total_array_size <= total_range_size
      result << [ARRAY_FORMAT].pack('C')
      result << new_indices.map(&:last).pack('C*')
    else
      result << [RANGE_FORMAT, ranges.size].pack('Cn')
      ranges.each { |range| result << range.pack('nC') }

      # "A sentinel GID follows the last range element and serves to
      # delimit the last range in the array. (The sentinel GID is set
      # equal to the number of glyphs in the font. That is, its value
      # is 1 greater than the last GID in the font)."
      result << [new_indices.size].pack('n')
    end
  end
end

Private Instance Methods

format_sym() click to toggle source
# File lib/ttfunk/table/cff/fd_selector.rb, line 182
def format_sym
  case @format
  when ARRAY_FORMAT then :array_format
  when RANGE_FORMAT then :range_format
  else
    raise Error, "unsupported fd select format '#{@format}'"
  end
end
parse!() click to toggle source
# File lib/ttfunk/table/cff/fd_selector.rb, line 145
def parse!
  @format = read(1, 'C').first
  @length = 1

  case format_sym
  when :array_format
    @n_glyphs = top_dict.charstrings_index.items_count
    data = io.read(n_glyphs)
    @length += data.bytesize
    @items_count = data.bytesize
    @entries = data.bytes

  when :range_format
    # +2 for sentinel GID, +2 for num_ranges
    num_ranges = read(2, 'n').first
    @length += (num_ranges * RANGE_ENTRY_SIZE) + 4

    ranges = Array.new(num_ranges) { read(RANGE_ENTRY_SIZE, 'nC') }

    @entries =
      ranges.each_cons(2).map { |first, second|
        first_gid, fd_index = first
        second_gid, = second
        [(first_gid...second_gid), fd_index]
      }

    # read the sentinel GID, otherwise known as the number of glyphs
    # in the font
    @n_glyphs = read(2, 'n').first

    last_start_gid, last_fd_index = ranges.last
    @entries << [(last_start_gid...(n_glyphs + 1)), last_fd_index]

    @items_count = entries.reduce(0) { |sum, entry| sum + entry.first.size }
  end
end
range_cache() click to toggle source
# File lib/ttfunk/table/cff/fd_selector.rb, line 125
def range_cache
  @range_cache ||= {}
end
rangify_gids(values) click to toggle source

values is an array of [new_gid, fd_index] pairs

# File lib/ttfunk/table/cff/fd_selector.rb, line 130
def rangify_gids(values)
  start_gid = 0

  [].tap do |ranges|
    values.each_cons(2) do |(_, first_idx), (sec_gid, sec_idx)|
      if first_idx != sec_idx
        ranges << [start_gid, first_idx]
        start_gid = sec_gid
      end
    end

    ranges << [start_gid, values.last[1]]
  end
end