ttfunk 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. data/CHANGELOG +2 -0
  2. data/README.rdoc +39 -0
  3. data/data/fonts/DejaVuSans.ttf +0 -0
  4. data/data/fonts/comicsans.ttf +0 -0
  5. data/examples/metrics.rb +46 -0
  6. data/lib/ttfunk/directory.rb +17 -0
  7. data/lib/ttfunk/encoding/mac_roman.rb +88 -0
  8. data/lib/ttfunk/encoding/windows_1252.rb +69 -0
  9. data/lib/ttfunk/reader.rb +44 -0
  10. data/lib/ttfunk/resource_file.rb +78 -0
  11. data/lib/ttfunk/subset/base.rb +141 -0
  12. data/lib/ttfunk/subset/mac_roman.rb +50 -0
  13. data/lib/ttfunk/subset/unicode.rb +48 -0
  14. data/lib/ttfunk/subset/unicode_8bit.rb +63 -0
  15. data/lib/ttfunk/subset/windows_1252.rb +55 -0
  16. data/lib/ttfunk/subset.rb +18 -0
  17. data/lib/ttfunk/subset_collection.rb +72 -0
  18. data/lib/ttfunk/table/cmap/format00.rb +54 -0
  19. data/lib/ttfunk/table/cmap/format04.rb +126 -0
  20. data/lib/ttfunk/table/cmap/subtable.rb +79 -0
  21. data/lib/ttfunk/table/cmap.rb +34 -0
  22. data/lib/ttfunk/table/glyf/compound.rb +81 -0
  23. data/lib/ttfunk/table/glyf/simple.rb +37 -0
  24. data/lib/ttfunk/table/glyf.rb +64 -0
  25. data/lib/ttfunk/table/head.rb +44 -0
  26. data/lib/ttfunk/table/hhea.rb +41 -0
  27. data/lib/ttfunk/table/hmtx.rb +47 -0
  28. data/lib/ttfunk/table/kern/format0.rb +62 -0
  29. data/lib/ttfunk/table/kern.rb +79 -0
  30. data/lib/ttfunk/table/loca.rb +43 -0
  31. data/lib/ttfunk/table/maxp.rb +40 -0
  32. data/lib/ttfunk/table/name.rb +125 -0
  33. data/lib/ttfunk/table/os2.rb +78 -0
  34. data/lib/ttfunk/table/post/format10.rb +43 -0
  35. data/lib/ttfunk/table/post/format20.rb +35 -0
  36. data/lib/ttfunk/table/post/format25.rb +23 -0
  37. data/lib/ttfunk/table/post/format30.rb +17 -0
  38. data/lib/ttfunk/table/post/format40.rb +17 -0
  39. data/lib/ttfunk/table/post.rb +91 -0
  40. data/lib/ttfunk/table/simple.rb +14 -0
  41. data/lib/ttfunk/table.rb +46 -0
  42. data/lib/ttfunk.rb +102 -0
  43. metadata +121 -0
@@ -0,0 +1,63 @@
1
+ require 'set'
2
+ require 'ttfunk/subset/base'
3
+
4
+ module TTFunk
5
+ module Subset
6
+ class Unicode8Bit < Base
7
+ def initialize(original)
8
+ super
9
+ @subset = { 0x20 => 0x20 }
10
+ @unicodes = { 0x20 => 0x20 }
11
+ @next = 0x21 # apparently, PDF's don't like to use chars between 0-31
12
+ end
13
+
14
+ def unicode?
15
+ true
16
+ end
17
+
18
+ def to_unicode_map
19
+ @subset.dup
20
+ end
21
+
22
+ def use(character)
23
+ if !@unicodes.key?(character)
24
+ @subset[@next] = character
25
+ @unicodes[character] = @next
26
+ @next += 1
27
+ end
28
+ end
29
+
30
+ def covers?(character)
31
+ @unicodes.key?(character) || @next < 256
32
+ end
33
+
34
+ def includes?(character)
35
+ @unicodes.key?(character)
36
+ end
37
+
38
+ def from_unicode(character)
39
+ @unicodes[character]
40
+ end
41
+
42
+ protected
43
+
44
+ def new_cmap_table(options)
45
+ mapping = @subset.inject({}) do |map, (code,unicode)|
46
+ map[code] = unicode_cmap[unicode]
47
+ map
48
+ end
49
+
50
+ # since we're mapping a subset of the unicode glyphs into an
51
+ # arbitrary 256-character space, the actual encoding we're
52
+ # using is irrelevant. We choose MacRoman because it's a 256-character
53
+ # encoding that happens to be well-supported in both TTF and
54
+ # PDF formats.
55
+ TTFunk::Table::Cmap.encode(mapping, :mac_roman)
56
+ end
57
+
58
+ def original_glyph_ids
59
+ ([0] + @unicodes.keys.map { |unicode| unicode_cmap[unicode] }).uniq.sort
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,55 @@
1
+ require 'set'
2
+ require 'ttfunk/subset/base'
3
+ require 'ttfunk/encoding/windows_1252'
4
+
5
+ module TTFunk
6
+ module Subset
7
+ class Windows1252 < Base
8
+ def initialize(original)
9
+ super
10
+ @subset = Array.new(256)
11
+ end
12
+
13
+ def to_unicode_map
14
+ Encoding::Windows1252::TO_UNICODE
15
+ end
16
+
17
+ def use(character)
18
+ @subset[Encoding::Windows1252::FROM_UNICODE[character]] = character
19
+ end
20
+
21
+ def covers?(character)
22
+ Encoding::Windows1252.covers?(character)
23
+ end
24
+
25
+ def includes?(character)
26
+ code = Encoding::Windows1252::FROM_UNICODE[character]
27
+ code && @subset[code]
28
+ end
29
+
30
+ def from_unicode(character)
31
+ Encoding::Windows1252::FROM_UNICODE[character]
32
+ end
33
+
34
+ protected
35
+
36
+ def new_cmap_table(options)
37
+ mapping = {}
38
+ @subset.each_with_index do |unicode, cp1252|
39
+ mapping[cp1252] = unicode_cmap[unicode] if cp1252
40
+ end
41
+
42
+ # yes, I really mean "mac roman". TTF has no cp1252 encoding, and the
43
+ # alternative would be to encode it using a format 4 unicode table, which
44
+ # is overkill. for our purposes, mac-roman suffices. (If we were building
45
+ # a _real_ font, instead of a PDF-embeddable subset, things would probably
46
+ # be different.)
47
+ TTFunk::Table::Cmap.encode(mapping, :mac_roman)
48
+ end
49
+
50
+ def original_glyph_ids
51
+ ([0] + @subset.map { |unicode| unicode && unicode_cmap[unicode] }).compact.uniq.sort
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,18 @@
1
+ require 'ttfunk/subset/unicode'
2
+ require 'ttfunk/subset/unicode_8bit'
3
+ require 'ttfunk/subset/mac_roman'
4
+ require 'ttfunk/subset/windows_1252'
5
+
6
+ module TTFunk
7
+ module Subset
8
+ def self.for(original, encoding)
9
+ case encoding.to_sym
10
+ when :unicode then Unicode.new(original)
11
+ when :unicode_8bit then Unicode8Bit.new(original)
12
+ when :mac_roman then MacRoman.new(original)
13
+ when :windows_1252 then Windows1252.new(original)
14
+ else raise NotImplementedError, "encoding #{encoding} is not supported"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,72 @@
1
+ require 'ttfunk/subset'
2
+
3
+ module TTFunk
4
+ class SubsetCollection
5
+ def initialize(original)
6
+ @original = original
7
+ @subsets = [Subset.for(@original, :mac_roman)]
8
+ end
9
+
10
+ def [](subset)
11
+ @subsets[subset]
12
+ end
13
+
14
+ # +characters+ should be an array of UTF-16 characters
15
+ def use(characters)
16
+ characters.each do |char|
17
+ covered = false
18
+ @subsets.each_with_index do |subset, i|
19
+ if subset.covers?(char)
20
+ subset.use(char)
21
+ covered = true
22
+ break
23
+ end
24
+ end
25
+
26
+ if !covered
27
+ @subsets << Subset.for(@original, :unicode_8bit)
28
+ @subsets.last.use(char)
29
+ end
30
+ end
31
+ end
32
+
33
+ # +characters+ should be an array of UTF-16 characters. Returns
34
+ # an array of subset chunks, where each chunk is another array of
35
+ # two elements. The first element is the subset number, and the
36
+ # second element is the string of characters to render with that
37
+ # font subset. The strings will be encoded for their subset font,
38
+ # and so may not look (in the raw) like what was passed in, but
39
+ # they will render correctly with the indicated subset font.
40
+ def encode(characters)
41
+ return [] if characters.empty?
42
+
43
+ # TODO: probably would be more optimal to nix the #use method,
44
+ # and merge it into this one, so it can be done in a single
45
+ # pass instead of two passes.
46
+ use(characters)
47
+
48
+ parts = []
49
+ current_subset = 0
50
+ current_char = 0
51
+ char = characters[current_char]
52
+
53
+ loop do
54
+ while @subsets[current_subset].includes?(char)
55
+ char = @subsets[current_subset].from_unicode(char)
56
+
57
+ if parts.empty? || parts.last[0] != current_subset
58
+ parts << [current_subset, char.chr]
59
+ else
60
+ parts.last[1] << char
61
+ end
62
+
63
+ current_char += 1
64
+ return parts if current_char >= characters.length
65
+ char = characters[current_char]
66
+ end
67
+
68
+ current_subset = (current_subset + 1) % @subsets.length
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,54 @@
1
+ require 'ttfunk/encoding/mac_roman'
2
+ require 'ttfunk/encoding/windows_1252'
3
+
4
+ module TTFunk
5
+ class Table
6
+ class Cmap
7
+
8
+ module Format00
9
+ attr_reader :language
10
+ attr_reader :code_map
11
+
12
+ # Expects a hash mapping character codes to glyph ids (where the
13
+ # glyph ids are from the original font). Returns a hash including
14
+ # a new map (:charmap) that maps the characters in charmap to a
15
+ # another hash containing both the old (:old) and new (:new) glyph
16
+ # ids. The returned hash also includes a :subtable key, which contains
17
+ # the encoded subtable for the given charmap.
18
+ def self.encode(charmap)
19
+ next_id = 0
20
+ glyph_indexes = Array.new(256, 0)
21
+ glyph_map = { 0 => 0 }
22
+
23
+ new_map = charmap.keys.sort.inject({}) do |map, code|
24
+ glyph_map[charmap[code]] ||= next_id += 1
25
+ map[code] = { :old => charmap[code], :new => glyph_map[charmap[code]] }
26
+ glyph_indexes[code] = glyph_map[charmap[code]]
27
+ map
28
+ end
29
+
30
+ # format, length, language, indices
31
+ subtable = [0, 262, 0, *glyph_indexes].pack("nnnC*")
32
+
33
+ { :charmap => new_map, :subtable => subtable, :max_glyph_id => next_id+1 }
34
+ end
35
+
36
+ def [](code)
37
+ @code_map[code] || 0
38
+ end
39
+
40
+ def supported?
41
+ true
42
+ end
43
+
44
+ private
45
+
46
+ def parse_cmap!
47
+ length, @language = read(4, "nn")
48
+ @code_map = read(256, "C*")
49
+ end
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,126 @@
1
+ module TTFunk
2
+ class Table
3
+ class Cmap
4
+
5
+ module Format04
6
+ attr_reader :language
7
+ attr_reader :code_map
8
+
9
+ # Expects a hash mapping character codes to glyph ids (where the
10
+ # glyph ids are from the original font). Returns a hash including
11
+ # a new map (:charmap) that maps the characters in charmap to a
12
+ # another hash containing both the old (:old) and new (:new) glyph
13
+ # ids. The returned hash also includes a :subtable key, which contains
14
+ # the encoded subtable for the given charmap.
15
+ def self.encode(charmap)
16
+ end_codes = []
17
+ start_codes = []
18
+ next_id = 0
19
+ last = difference = nil
20
+
21
+ glyph_map = { 0 => 0 }
22
+ new_map = charmap.keys.sort.inject({}) do |map, code|
23
+ old = charmap[code]
24
+ glyph_map[old] ||= next_id += 1
25
+ map[code] = { :old => old, :new => glyph_map[old] }
26
+
27
+ delta = glyph_map[old] - code
28
+ if last.nil? || delta != difference
29
+ end_codes << last if last
30
+ start_codes << code
31
+ difference = delta
32
+ end
33
+ last = code
34
+
35
+ map
36
+ end
37
+
38
+ end_codes << last if last
39
+ end_codes << 0xFFFF
40
+ start_codes << 0xFFFF
41
+ segcount = start_codes.length
42
+
43
+ # build the conversion tables
44
+ deltas = []
45
+ range_offsets = []
46
+ glyph_indices = []
47
+
48
+ offset = 0
49
+ start_codes.zip(end_codes).each_with_index do |(a, b), segment|
50
+ if a == 0xFFFF
51
+ deltas << 0
52
+ range_offsets << 0
53
+ break
54
+ end
55
+
56
+ start_glyph_id = new_map[a][:new]
57
+ if a - start_glyph_id >= 0x8000
58
+ deltas << 0
59
+ range_offsets << 2 * (glyph_indices.length + segcount - segment)
60
+ a.upto(b) { |code| glyph_indices << new_map[code][:new] }
61
+ else
62
+ deltas << -a + start_glyph_id
63
+ range_offsets << 0
64
+ end
65
+ offset += 2
66
+ end
67
+
68
+ # format, length, language
69
+ subtable = [4, 16 + 8 * segcount + 2 * glyph_indices.length, 0].pack("nnn")
70
+
71
+ search_range = 2 * 2 ** (Math.log(segcount) / Math.log(2)).to_i
72
+ entry_selector = (Math.log(search_range / 2) / Math.log(2)).to_i
73
+ range_shift = (2 * segcount) - search_range
74
+ subtable << [segcount * 2, search_range, entry_selector, range_shift].pack("nnnn")
75
+
76
+ subtable << end_codes.pack("n*") << "\0\0" << start_codes.pack("n*")
77
+ subtable << deltas.pack("n*") << range_offsets.pack("n*") << glyph_indices.pack("n*")
78
+
79
+ { :charmap => new_map, :subtable => subtable, :max_glyph_id => next_id+1 }
80
+ end
81
+
82
+ def [](code)
83
+ @code_map[code] || 0
84
+ end
85
+
86
+ def supported?
87
+ true
88
+ end
89
+
90
+ private
91
+
92
+ def parse_cmap!
93
+ length, @language, segcount_x2 = read(6, "nnn")
94
+ segcount = segcount_x2 / 2
95
+
96
+ io.read(6) # skip searching hints
97
+
98
+ end_code = read(segcount_x2, "n*")
99
+ io.read(2) # skip reserved value
100
+ start_code = read(segcount_x2, "n*")
101
+ id_delta = read_signed(segcount)
102
+ id_range_offset = read(segcount_x2, "n*")
103
+
104
+ glyph_ids = read(length - io.pos + @offset, "n*")
105
+
106
+ @code_map = {}
107
+
108
+ end_code.each_with_index do |tail, i|
109
+ start_code[i].upto(tail) do |code|
110
+ if id_range_offset[i].zero?
111
+ glyph_id = code + id_delta[i]
112
+ else
113
+ index = id_range_offset[i] / 2 + (code - start_code[i]) - (segcount - i)
114
+ glyph_id = glyph_ids[index] || 0 # because some TTF fonts are broken
115
+ glyph_id += id_delta[i] if glyph_id != 0
116
+ end
117
+
118
+ @code_map[code] = glyph_id & 0xFFFF
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,79 @@
1
+ require 'ttfunk/reader'
2
+
3
+ module TTFunk
4
+ class Table
5
+ class Cmap
6
+ class Subtable
7
+ include Reader
8
+
9
+ attr_reader :platform_id
10
+ attr_reader :encoding_id
11
+ attr_reader :format
12
+
13
+ ENCODING_MAPPINGS = {
14
+ :mac_roman => { :platform_id => 1, :encoding_id => 0 },
15
+ # use microsoft unicode, instead of generic unicode, for optimal windows support
16
+ :unicode => { :platform_id => 3, :encoding_id => 1 }
17
+ }
18
+
19
+ def self.encode(charmap, encoding)
20
+ case encoding
21
+ when :mac_roman
22
+ result = Format00.encode(charmap)
23
+ when :unicode
24
+ result = Format04.encode(charmap)
25
+ else
26
+ raise NotImplementedError, "encoding #{encoding.inspect} is not supported"
27
+ end
28
+
29
+ mapping = ENCODING_MAPPINGS[encoding]
30
+
31
+ # platform-id, encoding-id, offset
32
+ result[:subtable] = [mapping[:platform_id], mapping[:encoding_id],
33
+ 12, result[:subtable]].pack("nnNA*")
34
+
35
+ return result
36
+ end
37
+
38
+ def initialize(file, table_start)
39
+ @file = file
40
+ @platform_id, @encoding_id, @offset = read(8, "nnN")
41
+ @offset += table_start
42
+
43
+ parse_from(@offset) do
44
+ @format = read(2, "n").first
45
+
46
+ case @format
47
+ when 0 then extend(TTFunk::Table::Cmap::Format00)
48
+ when 4 then extend(TTFunk::Table::Cmap::Format04)
49
+ end
50
+
51
+ parse_cmap!
52
+ end
53
+ end
54
+
55
+ def unicode?
56
+ platform_id == 3 && encoding_id == 1 && format == 4 ||
57
+ platform_id == 0 && format == 4
58
+ end
59
+
60
+ def supported?
61
+ false
62
+ end
63
+
64
+ def [](code)
65
+ raise NotImplementedError, "cmap format #{@format} is not supported"
66
+ end
67
+
68
+ private
69
+
70
+ def parse_cmap!
71
+ # do nothing...
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ require 'ttfunk/table/cmap/format00'
79
+ require 'ttfunk/table/cmap/format04'
@@ -0,0 +1,34 @@
1
+ module TTFunk
2
+ class Table
3
+ class Cmap < Table
4
+ attr_reader :version
5
+ attr_reader :tables
6
+
7
+ def self.encode(charmap, encoding)
8
+ result = Cmap::Subtable.encode(charmap, encoding)
9
+
10
+ # pack 'version' and 'table-count'
11
+ result[:table] = [0, 1, result.delete(:subtable)].pack("nnA*")
12
+ return result
13
+ end
14
+
15
+ def unicode
16
+ @unicode ||= @tables.select { |table| table.unicode? }
17
+ end
18
+
19
+ private
20
+
21
+ def parse!
22
+ @version, table_count = read(4, "nn")
23
+ @tables = []
24
+
25
+ table_count.times do
26
+ @tables << Cmap::Subtable.new(file, offset)
27
+ end
28
+ end
29
+ end
30
+
31
+ end
32
+ end
33
+
34
+ require 'ttfunk/table/cmap/subtable'
@@ -0,0 +1,81 @@
1
+ require 'ttfunk/reader'
2
+
3
+ module TTFunk
4
+ class Table
5
+ class Glyf
6
+ class Compound
7
+ include Reader
8
+
9
+ ARG_1_AND_2_ARE_WORDS = 0x0001
10
+ WE_HAVE_A_SCALE = 0x0008
11
+ MORE_COMPONENTS = 0x0020
12
+ WE_HAVE_AN_X_AND_Y_SCALE = 0x0040
13
+ WE_HAVE_A_TWO_BY_TWO = 0x0080
14
+ WE_HAVE_INSTRUCTIONS = 0x0100
15
+
16
+ attr_reader :raw
17
+ attr_reader :x_min, :y_min, :x_max, :y_max
18
+ attr_reader :glyph_ids
19
+
20
+ Component = Struct.new(:flags, :glyph_index, :arg1, :arg2, :transform)
21
+
22
+ def initialize(raw, x_min, y_min, x_max, y_max)
23
+ @raw = raw
24
+ @x_min, @y_min, @x_max, @y_max = x_min, y_min, x_max, y_max
25
+
26
+ # Because TTFunk only cares about glyphs insofar as they (1) provide
27
+ # a bounding box for each glyph, and (2) can be rewritten into a
28
+ # font subset, we don't really care about the rest of the glyph data
29
+ # except as a whole. Thus, we don't actually decompose the glyph
30
+ # into it's parts--all we really care about are the locations within
31
+ # the raw string where the component glyph ids are stored, so that
32
+ # when we rewrite this glyph into a subset we can rewrite the
33
+ # component glyph-ids so they are correct for the subset.
34
+
35
+ @glyph_ids = []
36
+ @glyph_id_offsets = []
37
+ offset = 10 # 2 bytes for each of num-contours, min x/y, max x/y
38
+
39
+ loop do
40
+ flags, glyph_id = @raw[offset, 4].unpack("n*")
41
+ @glyph_ids << glyph_id
42
+ @glyph_id_offsets << offset + 2
43
+
44
+ break unless flags & MORE_COMPONENTS != 0
45
+ offset += 4
46
+
47
+ if flags & ARG_1_AND_2_ARE_WORDS != 0
48
+ offset += 4
49
+ else
50
+ offset += 2
51
+ end
52
+
53
+ if flags & WE_HAVE_A_TWO_BY_TWO != 0
54
+ offset += 8
55
+ elsif flags & WE_HAVE_AN_X_AND_Y_SCALE != 0
56
+ offset += 4
57
+ elsif flags & WE_HAVE_A_SCALE != 0
58
+ offset += 2
59
+ end
60
+ end
61
+ end
62
+
63
+ def compound?
64
+ true
65
+ end
66
+
67
+ def recode(mapping)
68
+ result = @raw.dup
69
+ new_ids = glyph_ids.map { |id| mapping[id] }
70
+
71
+ new_ids.zip(@glyph_id_offsets).each do |new_id, offset|
72
+ result[offset, 2] = [new_id].pack("n")
73
+ end
74
+
75
+ return result
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+
@@ -0,0 +1,37 @@
1
+ require 'ttfunk/reader'
2
+
3
+ module TTFunk
4
+ class Table
5
+ class Glyf
6
+ class Simple
7
+ attr_reader :raw
8
+ attr_reader :number_of_contours
9
+ attr_reader :x_min, :y_min, :x_max, :y_max
10
+
11
+ def initialize(raw, number_of_contours, x_min, y_min, x_max, y_max)
12
+ @raw = raw
13
+ @number_of_contours = number_of_contours
14
+ @x_min, @y_min = x_min, y_min
15
+ @x_max, @y_max = x_max, y_max
16
+
17
+ # Because TTFunk is, at this time, a library for simply pulling
18
+ # metrics out of font files, or for writing font subsets, we don't
19
+ # really care what the contours are for simple glyphs. We just
20
+ # care that we've got an entire glyph's definition. Also, a
21
+ # bounding box could be nice to know. Since we've got all that
22
+ # at this point, we don't need to worry about parsing the full
23
+ # contents of the glyph.
24
+ end
25
+
26
+ def compound?
27
+ false
28
+ end
29
+
30
+ def recode(mapping)
31
+ raw
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+
@@ -0,0 +1,64 @@
1
+ require 'ttfunk/table'
2
+
3
+ module TTFunk
4
+ class Table
5
+ class Glyf < Table
6
+ # Accepts a hash mapping (old) glyph-ids to glyph objects, and a hash
7
+ # mapping old glyph-ids to new glyph-ids.
8
+ #
9
+ # Returns a hash containing:
10
+ #
11
+ # * :table - a string representing the encoded 'glyf' table containing
12
+ # the given glyphs.
13
+ # * :offsets - an array of offsets for each glyph
14
+ def self.encode(glyphs, new2old, old2new)
15
+ result = { :table => "", :offsets => [] }
16
+
17
+ new2old.keys.sort.each do |new_id|
18
+ glyph = glyphs[new2old[new_id]]
19
+ result[:offsets] << result[:table].length
20
+ result[:table] << glyph.recode(old2new) if glyph
21
+ end
22
+
23
+ # include an offset at the end of the table, for use in computing the
24
+ # size of the last glyph
25
+ result[:offsets] << result[:table].length
26
+ return result
27
+ end
28
+
29
+ def for(glyph_id)
30
+ return @cache[glyph_id] if @cache.key?(glyph_id)
31
+
32
+ index = file.glyph_locations.index_of(glyph_id)
33
+ size = file.glyph_locations.size_of(glyph_id)
34
+
35
+ if size.zero? # blank glyph, e.g. space character
36
+ @cache[glyph_id] = nil
37
+ return nil
38
+ end
39
+
40
+ parse_from(offset + index) do
41
+ raw = io.read(size)
42
+ number_of_contours, x_min, y_min, x_max, y_max = raw.unpack("n5").map { |i| to_signed(i) }
43
+
44
+ @cache[glyph_id] = if number_of_contours == -1
45
+ Compound.new(raw, x_min, y_min, x_max, y_max)
46
+ else
47
+ Simple.new(raw, number_of_contours, x_min, y_min, x_max, y_max)
48
+ end
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def parse!
55
+ # because the glyf table is rather complex to parse, we defer
56
+ # the parse until we need a specific glyf, and then cache it.
57
+ @cache = {}
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ require 'ttfunk/table/glyf/compound'
64
+ require 'ttfunk/table/glyf/simple'