1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 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
|