File: index.rb

package info (click to toggle)
ruby-ttfunk 1.8.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 18,472 kB
  • sloc: ruby: 7,954; makefile: 7
file content (158 lines) | stat: -rw-r--r-- 3,947 bytes parent folder | download
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