ttfunk 1.4.0 → 1.5.0

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 (50) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/CHANGELOG.md +114 -0
  5. data/{README.rdoc → README.md} +12 -8
  6. data/lib/ttfunk.rb +27 -21
  7. data/lib/ttfunk/collection.rb +41 -0
  8. data/lib/ttfunk/directory.rb +10 -4
  9. data/lib/ttfunk/encoding/mac_roman.rb +50 -40
  10. data/lib/ttfunk/encoding/windows_1252.rb +28 -22
  11. data/lib/ttfunk/reader.rb +11 -10
  12. data/lib/ttfunk/resource_file.rb +28 -21
  13. data/lib/ttfunk/subset.rb +5 -5
  14. data/lib/ttfunk/subset/base.rb +66 -38
  15. data/lib/ttfunk/subset/mac_roman.rb +11 -10
  16. data/lib/ttfunk/subset/unicode.rb +10 -8
  17. data/lib/ttfunk/subset/unicode_8bit.rb +16 -16
  18. data/lib/ttfunk/subset/windows_1252.rb +16 -15
  19. data/lib/ttfunk/subset_collection.rb +7 -8
  20. data/lib/ttfunk/table.rb +3 -5
  21. data/lib/ttfunk/table/cmap.rb +13 -13
  22. data/lib/ttfunk/table/cmap/format00.rb +8 -10
  23. data/lib/ttfunk/table/cmap/format04.rb +37 -32
  24. data/lib/ttfunk/table/cmap/format06.rb +16 -16
  25. data/lib/ttfunk/table/cmap/format10.rb +22 -17
  26. data/lib/ttfunk/table/cmap/format12.rb +28 -22
  27. data/lib/ttfunk/table/cmap/subtable.rb +30 -23
  28. data/lib/ttfunk/table/glyf.rb +13 -11
  29. data/lib/ttfunk/table/glyf/compound.rb +13 -10
  30. data/lib/ttfunk/table/glyf/simple.rb +5 -4
  31. data/lib/ttfunk/table/head.rb +13 -13
  32. data/lib/ttfunk/table/hhea.rb +13 -13
  33. data/lib/ttfunk/table/hmtx.rb +23 -18
  34. data/lib/ttfunk/table/kern.rb +57 -48
  35. data/lib/ttfunk/table/kern/format0.rb +18 -10
  36. data/lib/ttfunk/table/loca.rb +9 -9
  37. data/lib/ttfunk/table/maxp.rb +9 -9
  38. data/lib/ttfunk/table/name.rb +65 -56
  39. data/lib/ttfunk/table/os2.rb +21 -21
  40. data/lib/ttfunk/table/post.rb +32 -32
  41. data/lib/ttfunk/table/post/format10.rb +33 -27
  42. data/lib/ttfunk/table/post/format20.rb +10 -10
  43. data/lib/ttfunk/table/post/format30.rb +5 -5
  44. data/lib/ttfunk/table/post/format40.rb +3 -3
  45. data/lib/ttfunk/table/sbix.rb +19 -10
  46. metadata +55 -32
  47. metadata.gz.sig +0 -0
  48. data/CHANGELOG +0 -5
  49. data/data/fonts/DejaVuSans.ttf +0 -0
  50. data/examples/metrics.rb +0 -45
@@ -20,7 +20,7 @@ module TTFunk
20
20
  end
21
21
 
22
22
  def use(character)
23
- if !@unicodes.key?(character)
23
+ unless @unicodes.key?(character)
24
24
  @subset[@next] = character
25
25
  @unicodes[character] = @next
26
26
  @next += 1
@@ -41,23 +41,23 @@ module TTFunk
41
41
 
42
42
  protected
43
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)
44
+ def new_cmap_table(_options)
45
+ mapping = @subset.each_with_object({}) do |(code, unicode), map|
46
+ map[code] = unicode_cmap[unicode]
47
+ map
56
48
  end
57
49
 
58
- def original_glyph_ids
59
- ([0] + @unicodes.keys.map { |unicode| unicode_cmap[unicode] }).uniq.sort
60
- end
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
61
  end
62
62
  end
63
63
  end
@@ -34,23 +34,24 @@ module TTFunk
34
34
 
35
35
  protected
36
36
 
37
- def new_cmap_table(options)
38
- mapping = {}
39
- @subset.each_with_index do |unicode, cp1252|
40
- mapping[cp1252] = unicode_cmap[unicode] if cp1252
41
- end
42
-
43
- # yes, I really mean "mac roman". TTF has no cp1252 encoding, and the
44
- # alternative would be to encode it using a format 4 unicode table, which
45
- # is overkill. for our purposes, mac-roman suffices. (If we were building
46
- # a _real_ font, instead of a PDF-embeddable subset, things would probably
47
- # be different.)
48
- TTFunk::Table::Cmap.encode(mapping, :mac_roman)
37
+ def new_cmap_table(_options)
38
+ mapping = {}
39
+ @subset.each_with_index do |unicode, cp1252|
40
+ mapping[cp1252] = unicode_cmap[unicode] if cp1252
49
41
  end
50
42
 
51
- def original_glyph_ids
52
- ([0] + @subset.map { |unicode| unicode && unicode_cmap[unicode] }).compact.uniq.sort
53
- end
43
+ # yes, I really mean "mac roman". TTF has no cp1252 encoding, and the
44
+ # alternative would be to encode it using a format 4 unicode table,
45
+ # which is overkill. for our purposes, mac-roman suffices. (If we were
46
+ # building a _real_ font, instead of a PDF-embeddable subset, things
47
+ # would probably be different.)
48
+ TTFunk::Table::Cmap.encode(mapping, :mac_roman)
49
+ end
50
+
51
+ def original_glyph_ids
52
+ ([0] + @subset.map { |unicode| unicode && unicode_cmap[unicode] })
53
+ .compact.uniq.sort
54
+ end
54
55
  end
55
56
  end
56
57
  end
@@ -15,15 +15,14 @@ module TTFunk
15
15
  def use(characters)
16
16
  characters.each do |char|
17
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
18
+ @subsets.each_with_index do |subset, _i|
19
+ next unless subset.covers?(char)
20
+ subset.use(char)
21
+ covered = true
22
+ break
24
23
  end
25
24
 
26
- if !covered
25
+ unless covered
27
26
  @subsets << Subset.for(@original, :unicode_8bit)
28
27
  @subsets.last.use(char)
29
28
  end
@@ -57,7 +56,7 @@ module TTFunk
57
56
  if parts.empty? || parts.last[0] != current_subset
58
57
  encoded_char = char.chr
59
58
  if encoded_char.respond_to?(:force_encoding)
60
- encoded_char.force_encoding("ASCII-8BIT")
59
+ encoded_char.force_encoding('ASCII-8BIT')
61
60
  end
62
61
  parts << [current_subset, encoded_char]
63
62
  else
data/lib/ttfunk/table.rb CHANGED
@@ -28,8 +28,6 @@ module TTFunk
28
28
  def raw
29
29
  if exists?
30
30
  parse_from(offset) { io.read(length) }
31
- else
32
- nil
33
31
  end
34
32
  end
35
33
 
@@ -39,8 +37,8 @@ module TTFunk
39
37
 
40
38
  private
41
39
 
42
- def parse!
43
- # do nothing, by default
44
- end
40
+ def parse!
41
+ # do nothing, by default
42
+ end
45
43
  end
46
44
  end
@@ -1,4 +1,4 @@
1
- module TTFunk
1
+ module TTFunk
2
2
  class Table
3
3
  class Cmap < Table
4
4
  attr_reader :version
@@ -8,29 +8,29 @@ module TTFunk
8
8
  result = Cmap::Subtable.encode(charmap, encoding)
9
9
 
10
10
  # pack 'version' and 'table-count'
11
- result[:table] = [0, 1, result.delete(:subtable)].pack("nnA*")
12
- return result
11
+ result[:table] = [0, 1, result.delete(:subtable)].pack('nnA*')
12
+ result
13
13
  end
14
14
 
15
15
  def unicode
16
- # Because most callers just call .first on the result, put tables with
16
+ # Because most callers just call .first on the result, put tables with
17
17
  # highest-number format first. Unsupported formats will be ignored.
18
- @unicode ||= @tables.select { |table| table.unicode? && table.supported? }
19
- .sort{|a,b| b.format <=> a.format }
18
+ @unicode ||= @tables
19
+ .select { |table| table.unicode? && table.supported? }
20
+ .sort { |a, b| b.format <=> a.format }
20
21
  end
21
22
 
22
23
  private
23
24
 
24
- def parse!
25
- @version, table_count = read(4, "nn")
26
- @tables = []
25
+ def parse!
26
+ @version, table_count = read(4, 'nn')
27
+ @tables = []
27
28
 
28
- table_count.times do
29
- @tables << Cmap::Subtable.new(file, offset)
30
- end
29
+ table_count.times do
30
+ @tables << Cmap::Subtable.new(file, offset)
31
31
  end
32
+ end
32
33
  end
33
-
34
34
  end
35
35
  end
36
36
 
@@ -4,7 +4,6 @@ require_relative '../../encoding/windows_1252'
4
4
  module TTFunk
5
5
  class Table
6
6
  class Cmap
7
-
8
7
  module Format00
9
8
  attr_reader :language
10
9
  attr_reader :code_map
@@ -20,17 +19,17 @@ module TTFunk
20
19
  glyph_indexes = Array.new(256, 0)
21
20
  glyph_map = { 0 => 0 }
22
21
 
23
- new_map = charmap.keys.sort.inject({}) do |map, code|
22
+ new_map = charmap.keys.sort.each_with_object({}) do |code, map|
24
23
  glyph_map[charmap[code]] ||= next_id += 1
25
- map[code] = { :old => charmap[code], :new => glyph_map[charmap[code]] }
24
+ map[code] = { old: charmap[code], new: glyph_map[charmap[code]] }
26
25
  glyph_indexes[code] = glyph_map[charmap[code]]
27
26
  map
28
27
  end
29
28
 
30
29
  # format, length, language, indices
31
- subtable = [0, 262, 0, *glyph_indexes].pack("nnnC*")
30
+ subtable = [0, 262, 0, *glyph_indexes].pack('nnnC*')
32
31
 
33
- { :charmap => new_map, :subtable => subtable, :max_glyph_id => next_id+1 }
32
+ { charmap: new_map, subtable: subtable, max_glyph_id: next_id + 1 }
34
33
  end
35
34
 
36
35
  def [](code)
@@ -43,12 +42,11 @@ module TTFunk
43
42
 
44
43
  private
45
44
 
46
- def parse_cmap!
47
- @language = read(4, "x2n")
48
- @code_map = read(256, "C*")
49
- end
45
+ def parse_cmap!
46
+ @language = read(4, 'x2n')
47
+ @code_map = read(256, 'C*')
48
+ end
50
49
  end
51
-
52
50
  end
53
51
  end
54
52
  end
@@ -1,7 +1,6 @@
1
1
  module TTFunk
2
2
  class Table
3
3
  class Cmap
4
-
5
4
  module Format04
6
5
  attr_reader :language
7
6
  attr_reader :code_map
@@ -19,10 +18,10 @@ module TTFunk
19
18
  last = difference = nil
20
19
 
21
20
  glyph_map = { 0 => 0 }
22
- new_map = charmap.keys.sort.inject({}) do |map, code|
21
+ new_map = charmap.keys.sort.each_with_object({}) do |code, map|
23
22
  old = charmap[code]
24
23
  glyph_map[old] ||= next_id += 1
25
- map[code] = { :old => old, :new => glyph_map[old] }
24
+ map[code] = { old: old, new: glyph_map[old] }
26
25
 
27
26
  delta = glyph_map[old] - code
28
27
  if last.nil? || delta != difference
@@ -66,17 +65,22 @@ module TTFunk
66
65
  end
67
66
 
68
67
  # format, length, language
69
- subtable = [4, 16 + 8 * segcount + 2 * glyph_indices.length, 0].pack("nnn")
68
+ subtable = [
69
+ 4, 16 + 8 * segcount + 2 * glyph_indices.length, 0
70
+ ].pack('nnn')
70
71
 
71
- search_range = 2 * 2 ** (Math.log(segcount) / Math.log(2)).to_i
72
+ search_range = 2 * 2**(Math.log(segcount) / Math.log(2)).to_i
72
73
  entry_selector = (Math.log(search_range / 2) / Math.log(2)).to_i
73
74
  range_shift = (2 * segcount) - search_range
74
- subtable << [segcount * 2, search_range, entry_selector, range_shift].pack("nnnn")
75
+ subtable << [
76
+ segcount * 2, search_range, entry_selector, range_shift
77
+ ].pack('nnnn')
75
78
 
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*")
79
+ subtable << end_codes.pack('n*') << "\0\0" << start_codes.pack('n*')
80
+ subtable << deltas.pack('n*') << range_offsets.pack('n*')
81
+ subtable << glyph_indices.pack('n*')
78
82
 
79
- { :charmap => new_map, :subtable => subtable, :max_glyph_id => next_id+1 }
83
+ { charmap: new_map, subtable: subtable, max_glyph_id: next_id + 1 }
80
84
  end
81
85
 
82
86
  def [](code)
@@ -89,38 +93,39 @@ module TTFunk
89
93
 
90
94
  private
91
95
 
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
96
+ def parse_cmap!
97
+ length, @language, segcount_x2 = read(6, 'nnn')
98
+ segcount = segcount_x2 / 2
97
99
 
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*")
100
+ io.read(6) # skip searching hints
103
101
 
104
- glyph_ids = read(length - io.pos + @offset, "n*")
102
+ end_code = read(segcount_x2, 'n*')
103
+ io.read(2) # skip reserved value
104
+ start_code = read(segcount_x2, 'n*')
105
+ id_delta = read_signed(segcount)
106
+ id_range_offset = read(segcount_x2, 'n*')
105
107
 
106
- @code_map = {}
108
+ glyph_ids = read(length - io.pos + @offset, 'n*')
107
109
 
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
110
+ @code_map = {}
117
111
 
118
- @code_map[code] = glyph_id & 0xFFFF
112
+ end_code.each_with_index do |tail, i|
113
+ start_code[i].upto(tail) do |code|
114
+ if id_range_offset[i] == 0
115
+ glyph_id = code + id_delta[i]
116
+ else
117
+ index = id_range_offset[i] / 2 +
118
+ (code - start_code[i]) - (segcount - i)
119
+ # Decause some TTF fonts are broken
120
+ glyph_id = glyph_ids[index] || 0
121
+ glyph_id += id_delta[i] if glyph_id != 0
119
122
  end
123
+
124
+ @code_map[code] = glyph_id & 0xFFFF
120
125
  end
121
126
  end
127
+ end
122
128
  end
123
-
124
129
  end
125
130
  end
126
131
  end
@@ -1,7 +1,6 @@
1
1
  module TTFunk
2
2
  class Table
3
3
  class Cmap
4
-
5
4
  module Format06
6
5
  attr_reader :language
7
6
  attr_reader :code_map
@@ -11,20 +10,22 @@ module TTFunk
11
10
  glyph_map = { 0 => 0 }
12
11
 
13
12
  sorted_chars = charmap.keys.sort
14
- low_char, high_char = sorted_chars.first, sorted_chars.last
15
- entry_count = (1+high_char-low_char)
13
+ low_char = sorted_chars.first
14
+ high_char = sorted_chars.last
15
+ entry_count = 1 + high_char - low_char
16
16
  glyph_indexes = Array.new(entry_count, 0)
17
17
 
18
- new_map = charmap.keys.sort.inject({}) do |map, code|
18
+ new_map = charmap.keys.sort.each_with_object({}) do |code, map|
19
19
  glyph_map[charmap[code]] ||= next_id += 1
20
- map[code] = { :old => charmap[code], :new => glyph_map[charmap[code]] }
20
+ map[code] = { old: charmap[code], new: glyph_map[charmap[code]] }
21
21
  glyph_indexes[code - low_char] = glyph_map[charmap[code]]
22
- map
23
22
  end
24
23
 
25
- subtable = [6, 10+entry_count*2, 0, low_char, entry_count, *glyph_indexes].pack('n*')
24
+ subtable = [
25
+ 6, 10 + entry_count * 2, 0, low_char, entry_count, *glyph_indexes
26
+ ].pack('n*')
26
27
 
27
- { :charmap => new_map, :subtable => subtable, :max_glyph_id => next_id+1 }
28
+ { charmap: new_map, subtable: subtable, max_glyph_id: next_id + 1 }
28
29
  end
29
30
 
30
31
  def [](code)
@@ -36,16 +37,15 @@ module TTFunk
36
37
  end
37
38
 
38
39
  private
39
- def parse_cmap!
40
- @language, firstcode, entrycount = read(8, 'x2nnn')
41
- @code_map = {}
42
- (firstcode...(firstcode+entrycount)).each do |code|
43
- @code_map[code] = read(2, 'n').first & 0xFFFF
44
- end
45
- end
46
40
 
41
+ def parse_cmap!
42
+ @language, firstcode, entrycount = read(8, 'x2nnn')
43
+ @code_map = {}
44
+ (firstcode...(firstcode + entrycount)).each do |code|
45
+ @code_map[code] = read(2, 'n').first & 0xFFFF
46
+ end
47
+ end
47
48
  end
48
-
49
49
  end
50
50
  end
51
51
  end
@@ -1,7 +1,6 @@
1
1
  module TTFunk
2
2
  class Table
3
3
  class Cmap
4
-
5
4
  module Format10
6
5
  attr_reader :language
7
6
  attr_reader :code_map
@@ -11,20 +10,23 @@ module TTFunk
11
10
  glyph_map = { 0 => 0 }
12
11
 
13
12
  sorted_chars = charmap.keys.sort
14
- low_char, high_char = sorted_chars.first, sorted_chars.last
15
- entry_count = (1+high_char-low_char)
13
+ low_char = sorted_chars.first
14
+ high_char = sorted_chars.last
15
+ entry_count = 1 + high_char - low_char
16
16
  glyph_indexes = Array.new(entry_count, 0)
17
17
 
18
- new_map = charmap.keys.sort.inject({}) do |map, code|
18
+ new_map = charmap.keys.sort.each_with_object({}) do |code, map|
19
19
  glyph_map[charmap[code]] ||= next_id += 1
20
- map[code] = { :old => charmap[code], :new => glyph_map[charmap[code]] }
20
+ map[code] = { old: charmap[code], new: glyph_map[charmap[code]] }
21
21
  glyph_indexes[code - low_char] = glyph_map[charmap[code]]
22
- map
23
22
  end
24
23
 
25
- subtable = [10, 0, 20+entry_count*4, 0, low_char, entry_count, *glyph_indexes].pack('nnN*')
24
+ subtable = [
25
+ 10, 0, 20 + entry_count * 4, 0, low_char, entry_count,
26
+ *glyph_indexes
27
+ ].pack('nnN*')
26
28
 
27
- { :charmap => new_map, :subtable => subtable, :max_glyph_id => next_id+1 }
29
+ { charmap: new_map, subtable: subtable, max_glyph_id: next_id + 1 }
28
30
  end
29
31
 
30
32
  def [](code)
@@ -36,17 +38,20 @@ module TTFunk
36
38
  end
37
39
 
38
40
  private
39
- def parse_cmap!
40
- fractional_version, @language, firstcode, entrycount = read(18, 'nx4NNN')
41
- raise NotImplementedError, "cmap version 10.#{fractional_version} is not supported" if fractional_version != 0
42
- @code_map = {}
43
- (firstcode...(firstcode+entrycount)).each do |code|
44
- @code_map[code] = read(2, 'n').first & 0xFFFF
45
- end
46
- end
47
41
 
42
+ def parse_cmap!
43
+ fractional_version, @language, firstcode, entrycount =
44
+ read(18, 'nx4NNN')
45
+ if fractional_version != 0
46
+ raise NotImplementedError,
47
+ "cmap version 10.#{fractional_version} is not supported"
48
+ end
49
+ @code_map = {}
50
+ (firstcode...(firstcode + entrycount)).each do |code|
51
+ @code_map[code] = read(2, 'n').first & 0xFFFF
52
+ end
53
+ end
48
54
  end
49
-
50
55
  end
51
56
  end
52
57
  end