This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user