File: format04.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 (160 lines) | stat: -rw-r--r-- 5,159 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
159
160
# frozen_string_literal: true

module TTFunk
  class Table
    class Cmap
      # Format 4: Segment mapping to delta values.
      #
      # This module conditionally extends {TTFunk::Table::Cmap::Subtable}.
      module Format04
        # Language.
        # @return [Integer]
        attr_reader :language

        # Code map.
        # @return [Hash{Integer => Integer}]
        attr_reader :code_map

        # Encode the encoding record to format 4.
        #
        # @param charmap [Hash{Integer => Integer}] a hash mapping character
        #   codes to glyph IDs from the original font.
        # @return [Hash]
        #   * `:charmap` (<tt>Hash{Integer => Hash}</tt>) keys are the characrers in
        #     `charset`, values are hashes:
        #     * `:old` (<tt>Integer</tt>) - glyph ID in the original font.
        #     * `:new` (<tt>Integer</tt>) - glyph ID in the subset font.
        #     that maps the characters in charmap to a
        #   * `:subtable` (<tt>String</tt>) - serialized encoding record.
        #   * `:max_glyph_id` (<tt>Integer</tt>) - maximum glyph ID in the new font.
        def self.encode(charmap)
          end_codes = []
          start_codes = []
          next_id = 0
          last = difference = nil

          glyph_map = { 0 => 0 }
          new_map =
            charmap.keys.sort.each_with_object({}) do |code, map|
              old = charmap[code]
              glyph_map[old] ||= next_id += 1
              map[code] = { old: old, new: glyph_map[old] }

              delta = glyph_map[old] - code
              if last.nil? || delta != difference
                end_codes << last if last
                start_codes << code
                difference = delta
              end
              last = code

              map
            end

          end_codes << last if last
          end_codes << 0xFFFF
          start_codes << 0xFFFF
          segcount = start_codes.length

          # build the conversion tables
          deltas = []
          range_offsets = []
          glyph_indices = []
          offset = 0

          start_codes.zip(end_codes).each_with_index do |(a, b), segment|
            if a == 0xFFFF
              # We want the final 0xFFFF code to map to glyph 0.
              # The glyph index is calculated as glyph = charcode + delta,
              # which means that delta must be -0xFFFF to map character code
              # 0xFFFF to glyph 0.
              deltas << -0xFFFF
              range_offsets << 0
              break
            end

            start_glyph_id = new_map[a][:new]

            if a - start_glyph_id >= 0x8000
              deltas << 0
              range_offsets << (2 * (glyph_indices.length + segcount - segment))
              a.upto(b) { |code| glyph_indices << new_map[code][:new] }
            else
              deltas << (-a + start_glyph_id)
              range_offsets << 0
            end

            offset += 2
          end

          # format, length, language
          subtable = [
            4, 16 + (8 * segcount) + (2 * glyph_indices.length), 0,
          ].pack('nnn')

          search_range = 2 * (2**Integer(Math.log(segcount) / Math.log(2)))
          entry_selector = Integer(Math.log(search_range / 2) / Math.log(2))
          range_shift = (2 * segcount) - search_range
          subtable << [
            segcount * 2, search_range, entry_selector, range_shift,
          ].pack('nnnn')

          subtable << end_codes.pack('n*') << "\0\0" << start_codes.pack('n*')
          subtable << deltas.pack('n*') << range_offsets.pack('n*')
          subtable << glyph_indices.pack('n*')

          { charmap: new_map, subtable: subtable, max_glyph_id: next_id + 1 }
        end

        # Get glyph ID for character code.
        #
        # @param code [Integer] character code.
        # @return [Integer] glyph ID.
        def [](code)
          @code_map[code] || 0
        end

        # Is this encoding record format supported?
        #
        # @return [true]
        def supported?
          true
        end

        private

        def parse_cmap!
          length, @language, segcount_x2 = read(6, 'nnn')
          segcount = segcount_x2 / 2

          io.read(6) # skip searching hints

          end_code = read(segcount_x2, 'n*')
          io.read(2) # skip reserved value
          start_code = read(segcount_x2, 'n*')
          id_delta = read_signed(segcount)
          id_range_offset = read(segcount_x2, 'n*')

          glyph_ids = read(length - io.pos + @offset, 'n*')

          @code_map = {}

          end_code.each_with_index do |tail, i|
            start_code[i].upto(tail) do |code|
              if (id_range_offset[i]).zero?
                glyph_id = code + id_delta[i]
              else
                index = (id_range_offset[i] / 2) + (code - start_code[i]) - (segcount - i)
                # Because some TTF fonts are broken
                glyph_id = glyph_ids[index] || 0
                glyph_id += id_delta[i] if glyph_id != 0
              end

              @code_map[code] = glyph_id & 0xFFFF
            end
          end
        end
      end
    end
  end
end