159 lines
3.9 KiB
Ruby
159 lines
3.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module TTFunk
|
|
class Table
|
|
class Cff < TTFunk::Table
|
|
# CFF Index.
|
|
class Index < TTFunk::SubTable
|
|
include Enumerable
|
|
|
|
# Get value by index.
|
|
#
|
|
# @param index [Integer]
|
|
# @return [any]
|
|
def [](index)
|
|
return if index >= items_count
|
|
|
|
entry_cache[index] ||=
|
|
decode_item(
|
|
index,
|
|
data_reference_offset + offsets[index],
|
|
offsets[index + 1] - offsets[index],
|
|
)
|
|
end
|
|
|
|
# Iterate over index items.
|
|
#
|
|
# @overload each()
|
|
# @yieldparam item [any]
|
|
# @return [void]
|
|
# @overload each()
|
|
# @return [Enumerator]
|
|
def each(&block)
|
|
return to_enum(__method__) unless block
|
|
|
|
items_count.times do |i|
|
|
yield(self[i])
|
|
end
|
|
end
|
|
|
|
# Numer of items in this index.
|
|
#
|
|
# @return [Integer]
|
|
def items_count
|
|
items.length
|
|
end
|
|
|
|
# Encode index.
|
|
#
|
|
# @param args all arguments are passed to `encode_item` method.
|
|
# @return [TTFunk::EncodedString]
|
|
def encode(*args)
|
|
new_items = encode_items(*args)
|
|
|
|
if new_items.empty?
|
|
return [0].pack('n')
|
|
end
|
|
|
|
if new_items.length > 0xffff
|
|
raise Error, 'Too many items in a CFF index'
|
|
end
|
|
|
|
offsets_array =
|
|
new_items
|
|
.each_with_object([1]) { |item, offsets|
|
|
offsets << (offsets.last + item.length)
|
|
}
|
|
|
|
offset_size = (offsets_array.last.bit_length / 8.0).ceil
|
|
|
|
offsets_array.map! { |offset| encode_offset(offset, offset_size) }
|
|
|
|
EncodedString.new.concat(
|
|
[new_items.length, offset_size].pack('nC'),
|
|
*offsets_array,
|
|
*new_items,
|
|
)
|
|
end
|
|
|
|
private
|
|
|
|
attr_reader :items
|
|
attr_reader :offsets
|
|
attr_reader :data_reference_offset
|
|
|
|
def entry_cache
|
|
@entry_cache ||= {}
|
|
end
|
|
|
|
# Returns an array of EncodedString elements (plain strings,
|
|
# placeholders, or EncodedString instances). Each element is supposed to
|
|
# represent an encoded item.
|
|
#
|
|
# This is the place to do all the filtering, reordering, or individual
|
|
# item encoding.
|
|
#
|
|
# It gets all the arguments `encode` gets.
|
|
def encode_items(*)
|
|
items
|
|
end
|
|
|
|
# By default do nothing
|
|
def decode_item(index, _offset, _length)
|
|
items[index]
|
|
end
|
|
|
|
def encode_offset(offset, offset_size)
|
|
case offset_size
|
|
when 1
|
|
[offset].pack('C')
|
|
when 2
|
|
[offset].pack('n')
|
|
when 3
|
|
[offset].pack('N')[1..]
|
|
when 4
|
|
[offset].pack('N')
|
|
end
|
|
end
|
|
|
|
def parse!
|
|
@entry_cache = {}
|
|
|
|
num_entries = read(2, 'n').first
|
|
|
|
if num_entries.zero?
|
|
@length = 2
|
|
@items = []
|
|
return
|
|
end
|
|
|
|
offset_size = read(1, 'C').first
|
|
|
|
@offsets =
|
|
Array.new(num_entries + 1) {
|
|
unpack_offset(io.read(offset_size), offset_size)
|
|
}
|
|
|
|
@data_reference_offset = table_offset + 3 + (offsets.length * offset_size) - 1
|
|
|
|
@length =
|
|
2 + # num entries
|
|
1 + # offset size
|
|
(offsets.length * offset_size) + # offsets
|
|
offsets.last - 1 # items
|
|
|
|
@items =
|
|
offsets.each_cons(2).map { |offset, next_offset|
|
|
io.read(next_offset - offset)
|
|
}
|
|
end
|
|
|
|
def unpack_offset(offset_data, offset_size)
|
|
padding = "\x00" * (4 - offset_size)
|
|
(padding + offset_data).unpack1('N')
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|