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
@@ -11,67 +11,76 @@ module TTFunk
11
11
  tables = kerning.tables.map { |table| table.recode(mapping) }.compact
12
12
  return nil if tables.empty?
13
13
 
14
- [0, tables.length, tables.join].pack("nnA*")
14
+ [0, tables.length, tables.join].pack('nnA*')
15
15
  end
16
16
 
17
17
  private
18
18
 
19
- def parse!
20
- @version, num_tables = read(4, "n*")
21
- @tables = []
19
+ def parse!
20
+ @version, num_tables = read(4, 'n*')
21
+ @tables = []
22
22
 
23
- if @version == 1 # Mac OS X fonts
24
- @version = (@version << 16) + num_tables
25
- num_tables = read(4, "N").first
26
- parse_version_1_tables(num_tables)
27
- else
28
- parse_version_0_tables(num_tables)
29
- end
23
+ if @version == 1 # Mac OS X fonts
24
+ @version = (@version << 16) + num_tables
25
+ num_tables = read(4, 'N').first
26
+ parse_version_1_tables(num_tables)
27
+ else
28
+ parse_version_0_tables(num_tables)
30
29
  end
30
+ end
31
31
 
32
- def parse_version_0_tables(num_tables)
33
- # It looks like some MS fonts report their kerning subtable lengths
34
- # wrong. In one case, the length was reported to be some 19366, and yet
35
- # the table also claimed to hold 14148 pairs (each pair consisting of 6 bytes).
36
- # You do the math!
37
- #
38
- # We're going to assume that the microsoft fonts hold only a single kerning
39
- # subtable, which occupies the entire length of the kerning table. Worst
40
- # case, we lose any other subtables that the font contains, but it's better
41
- # than reading a truncated kerning table.
42
- #
43
- # And what's more, it appears to work. So.
44
- version, length, coverage = read(6, "n*")
45
- format = coverage >> 8
32
+ def parse_version_0_tables(_num_tables)
33
+ # It looks like some MS fonts report their kerning subtable lengths
34
+ # wrong. In one case, the length was reported to be some 19366, and yet
35
+ # the table also claimed to hold 14148 pairs (each pair consisting of
36
+ # 6 bytes). You do the math!
37
+ #
38
+ # We're going to assume that the microsoft fonts hold only a single
39
+ # kerning subtable, which occupies the entire length of the kerning
40
+ # table. Worst case, we lose any other subtables that the font contains,
41
+ # but it's better than reading a truncated kerning table.
42
+ #
43
+ # And what's more, it appears to work. So.
44
+ version, length, coverage = read(6, 'n*')
45
+ format = coverage >> 8
46
46
 
47
- add_table format, :version => version, :length => length,
48
- :coverage => coverage, :data => raw[10..-1],
49
- :vertical => (coverage & 0x1 == 0),
50
- :minimum => (coverage & 0x2 != 0),
51
- :cross => (coverage & 0x4 != 0),
52
- :override => (coverage & 0x8 != 0)
53
- end
47
+ add_table(
48
+ format,
49
+ version: version,
50
+ length: length,
51
+ coverage: coverage,
52
+ data: raw[10..-1],
53
+ vertical: (coverage & 0x1 == 0),
54
+ minimum: (coverage & 0x2 != 0),
55
+ cross: (coverage & 0x4 != 0),
56
+ override: (coverage & 0x8 != 0)
57
+ )
58
+ end
54
59
 
55
- def parse_version_1_tables(num_tables)
56
- num_tables.times do
57
- length, coverage, tuple_index = read(8, "Nnn")
58
- format = coverage & 0x0FF
60
+ def parse_version_1_tables(num_tables)
61
+ num_tables.times do
62
+ length, coverage, tuple_index = read(8, 'Nnn')
63
+ format = coverage & 0x0FF
59
64
 
60
- add_table format, :length => length, :coverage => coverage,
61
- :tuple_index => tuple_index, :data => io.read(length-8),
62
- :vertical => (coverage & 0x8000 != 0),
63
- :cross => (coverage & 0x4000 != 0),
64
- :variation => (coverage & 0x2000 != 0)
65
- end
65
+ add_table(
66
+ format,
67
+ length: length,
68
+ coverage: coverage,
69
+ tuple_index: tuple_index,
70
+ data: io.read(length - 8),
71
+ vertical: (coverage & 0x8000 != 0),
72
+ cross: (coverage & 0x4000 != 0),
73
+ variation: (coverage & 0x2000 != 0)
74
+ )
66
75
  end
76
+ end
67
77
 
68
- def add_table(format, attributes={})
69
- if format == 0
70
- @tables << Kern::Format0.new(attributes)
71
- else
72
- # silently ignore unsupported kerning tables
73
- end
78
+ def add_table(format, attributes = {})
79
+ if format == 0
80
+ @tables << Kern::Format0.new(attributes)
74
81
  end
82
+ # Unsupported kerning tables are silently ignored
83
+ end
75
84
  end
76
85
  end
77
86
  end
@@ -9,17 +9,18 @@ module TTFunk
9
9
  attr_reader :attributes
10
10
  attr_reader :pairs
11
11
 
12
- def initialize(attributes={})
12
+ def initialize(attributes = {})
13
13
  @attributes = attributes
14
14
 
15
- num_pairs, *pairs = attributes.delete(:data).unpack("nx6n*")
15
+ num_pairs, *pairs = attributes.delete(:data).unpack('nx6n*')
16
16
 
17
17
  @pairs = {}
18
18
  num_pairs.times do |i|
19
- break if i*3+2 > pairs.length # sanity check, in case there's a bad length somewhere
20
- left = pairs[i*3]
21
- right = pairs[i*3+1]
22
- value = to_signed(pairs[i*3+2])
19
+ # sanity check, in case there's a bad length somewhere
20
+ break if i * 3 + 2 > pairs.length
21
+ left = pairs[i * 3]
22
+ right = pairs[i * 3 + 1]
23
+ value = to_signed(pairs[i * 3 + 2])
23
24
  @pairs[[left, right]] = value
24
25
  end
25
26
  end
@@ -47,13 +48,20 @@ module TTFunk
47
48
  return nil if subset.empty?
48
49
 
49
50
  num_pairs = subset.length
50
- search_range = 2 * 2 ** (Math.log(num_pairs) / Math.log(2)).to_i
51
+ search_range = 2 * 2**(Math.log(num_pairs) / Math.log(2)).to_i
51
52
  entry_selector = (Math.log(search_range / 2) / Math.log(2)).to_i
52
53
  range_shift = (2 * num_pairs) - search_range
53
54
 
54
- [attributes[:version], num_pairs * 6 + 14, attributes[:coverage],
55
- num_pairs, search_range, entry_selector, range_shift, subset].
56
- flatten.pack("n*")
55
+ [
56
+ attributes[:version],
57
+ num_pairs * 6 + 14,
58
+ attributes[:coverage],
59
+ num_pairs,
60
+ search_range,
61
+ entry_selector,
62
+ range_shift,
63
+ subset
64
+ ].flatten.pack('n*')
57
65
  end
58
66
  end
59
67
  end
@@ -14,9 +14,9 @@ module TTFunk
14
14
  # * :type - the type of offset (to be encoded in the 'head' table)
15
15
  def self.encode(offsets)
16
16
  if offsets.any? { |ofs| ofs > 0xFFFF }
17
- { :type => 1, :table => offsets.pack("N*") }
17
+ { type: 1, table: offsets.pack('N*') }
18
18
  else
19
- { :type => 0, :table => offsets.map { |o| o/2 }.pack("n*") }
19
+ { type: 0, table: offsets.map { |o| o / 2 }.pack('n*') }
20
20
  end
21
21
  end
22
22
 
@@ -25,19 +25,19 @@ module TTFunk
25
25
  end
26
26
 
27
27
  def size_of(glyph_id)
28
- @offsets[glyph_id+1] - @offsets[glyph_id]
28
+ @offsets[glyph_id + 1] - @offsets[glyph_id]
29
29
  end
30
30
 
31
31
  private
32
32
 
33
- def parse!
34
- type = file.header.index_to_loc_format == 0 ? "n" : "N"
35
- @offsets = read(length, "#{type}*")
33
+ def parse!
34
+ type = file.header.index_to_loc_format == 0 ? 'n' : 'N'
35
+ @offsets = read(length, "#{type}*")
36
36
 
37
- if file.header.index_to_loc_format == 0
38
- @offsets.map! { |v| v * 2 }
39
- end
37
+ if file.header.index_to_loc_format == 0
38
+ @offsets.map! { |v| v * 2 }
40
39
  end
40
+ end
41
41
  end
42
42
  end
43
43
  end
@@ -22,19 +22,19 @@ module TTFunk
22
22
  def self.encode(maxp, mapping)
23
23
  num_glyphs = mapping.length
24
24
  raw = maxp.raw
25
- raw[4,2] = [num_glyphs].pack("n")
26
- return raw
25
+ raw[4, 2] = [num_glyphs].pack('n')
26
+ raw
27
27
  end
28
28
 
29
29
  private
30
30
 
31
- def parse!
32
- @version, @num_glyphs, @max_points, @max_contours, @max_component_points,
33
- @max_component_contours, @max_zones, @max_twilight_points, @max_storage,
34
- @max_function_defs, @max_instruction_defs, @max_stack_elements,
35
- @max_size_of_instructions, @max_component_elements, @max_component_depth =
36
- read(length, "Nn*")
37
- end
31
+ def parse!
32
+ @version, @num_glyphs, @max_points, @max_contours,
33
+ @max_component_points, @max_component_contours, @max_zones,
34
+ @max_twilight_points, @max_storage, @max_function_defs,
35
+ @max_instruction_defs, @max_stack_elements, @max_size_of_instructions,
36
+ @max_component_elements, @max_component_depth = read(length, 'Nn*')
37
+ end
38
38
  end
39
39
  end
40
40
  end
@@ -1,4 +1,5 @@
1
1
  require_relative '../table'
2
+ require 'digest/sha1'
2
3
 
3
4
  module TTFunk
4
5
  class Table
@@ -16,9 +17,9 @@ module TTFunk
16
17
  end
17
18
 
18
19
  def strip_extended
19
- stripped = gsub(/[\x00-\x19\x80-\xff]/n, "")
20
- stripped = "[not-postscript]" if stripped.empty?
21
- return stripped
20
+ stripped = gsub(/[\x00-\x19\x80-\xff]/n, '')
21
+ stripped = '[not-postscript]' if stripped.empty?
22
+ stripped
22
23
  end
23
24
  end
24
25
 
@@ -43,24 +44,26 @@ module TTFunk
43
44
  attr_reader :compatible_full
44
45
  attr_reader :sample_text
45
46
 
46
- @@subset_tag = "AAAAAA"
47
+ def self.encode(names, key = '')
48
+ tag = Digest::SHA1.hexdigest(key)[0, 6]
47
49
 
48
- def self.encode(names)
49
- tag = @@subset_tag.dup
50
- @@subset_tag.succ!
51
-
52
- postscript_name = Name::String.new("#{tag}+#{names.postscript_name}", 1, 0, 0)
50
+ postscript_name = Name::String.new(
51
+ "#{tag}+#{names.postscript_name}", 1, 0, 0
52
+ )
53
53
 
54
54
  strings = names.strings.dup
55
55
  strings[6] = [postscript_name]
56
56
  str_count = strings.inject(0) { |sum, (_, list)| sum + list.length }
57
57
 
58
- table = [0, str_count, 6 + 12 * str_count].pack("n*")
59
- strtable = ""
58
+ table = [0, str_count, 6 + 12 * str_count].pack('n*')
59
+ strtable = ''
60
60
 
61
61
  strings.each do |id, list|
62
62
  list.each do |string|
63
- table << [string.platform_id, string.encoding_id, string.language_id, id, string.length, strtable.length].pack("n*")
63
+ table << [
64
+ string.platform_id, string.encoding_id, string.language_id, id,
65
+ string.length, strtable.length
66
+ ].pack('n*')
64
67
  strtable << string
65
68
  end
66
69
  end
@@ -70,56 +73,62 @@ module TTFunk
70
73
 
71
74
  def postscript_name
72
75
  return @postscript_name if @postscript_name
73
- font_family.first || "unnamed"
76
+ font_family.first || 'unnamed'
74
77
  end
75
78
 
76
79
  private
77
80
 
78
- def parse!
79
- count, string_offset = read(6, "x2n*")
80
-
81
- entries = []
82
- count.times do
83
- platform, encoding, language, id, length, start_offset = read(12, "n*")
84
- entries << {
85
- :platform_id => platform,
86
- :encoding_id => encoding,
87
- :language_id => language,
88
- :name_id => id,
89
- :length => length,
90
- :offset => offset + string_offset + start_offset
91
- }
92
- end
93
-
94
- @strings = Hash.new { |h,k| h[k] = [] }
95
-
96
- count.times do |i|
97
- io.pos = entries[i][:offset]
98
- text = io.read(entries[i][:length])
99
- @strings[entries[i][:name_id]] << Name::String.new(text,
100
- entries[i][:platform_id], entries[i][:encoding_id], entries[i][:language_id])
101
- end
81
+ def parse!
82
+ count, string_offset = read(6, 'x2n*')
83
+
84
+ entries = []
85
+ count.times do
86
+ platform, encoding, language, id, length, start_offset =
87
+ read(12, 'n*')
88
+ entries << {
89
+ platform_id: platform,
90
+ encoding_id: encoding,
91
+ language_id: language,
92
+ name_id: id,
93
+ length: length,
94
+ offset: offset + string_offset + start_offset
95
+ }
96
+ end
102
97
 
103
- @copyright = @strings[0]
104
- @font_family = @strings[1]
105
- @font_subfamily = @strings[2]
106
- @unique_subfamily = @strings[3]
107
- @font_name = @strings[4]
108
- @version = @strings[5]
109
- @postscript_name = @strings[6].first.strip_extended # should only be ONE postscript name
110
- @trademark = @strings[7]
111
- @manufacturer = @strings[8]
112
- @designer = @strings[9]
113
- @description = @strings[10]
114
- @vendor_url = @strings[11]
115
- @designer_url = @strings[12]
116
- @license = @strings[13]
117
- @license_url = @strings[14]
118
- @preferred_family = @strings[16]
119
- @preferred_subfamily = @strings[17]
120
- @compatible_full = @strings[18]
121
- @sample_text = @strings[19]
98
+ @strings = Hash.new { |h, k| h[k] = [] }
99
+
100
+ count.times do |i|
101
+ io.pos = entries[i][:offset]
102
+ text = io.read(entries[i][:length])
103
+ @strings[entries[i][:name_id]] << Name::String.new(
104
+ text,
105
+ entries[i][:platform_id],
106
+ entries[i][:encoding_id],
107
+ entries[i][:language_id]
108
+ )
122
109
  end
110
+
111
+ @copyright = @strings[0]
112
+ @font_family = @strings[1]
113
+ @font_subfamily = @strings[2]
114
+ @unique_subfamily = @strings[3]
115
+ @font_name = @strings[4]
116
+ @version = @strings[5]
117
+ # should only be ONE postscript name
118
+ @postscript_name = @strings[6].first.strip_extended
119
+ @trademark = @strings[7]
120
+ @manufacturer = @strings[8]
121
+ @designer = @strings[9]
122
+ @description = @strings[10]
123
+ @vendor_url = @strings[11]
124
+ @designer_url = @strings[12]
125
+ @license = @strings[13]
126
+ @license_url = @strings[14]
127
+ @preferred_family = @strings[16]
128
+ @preferred_subfamily = @strings[17]
129
+ @compatible_full = @strings[18]
130
+ @sample_text = @strings[19]
131
+ end
123
132
  end
124
133
  end
125
134
  end
@@ -41,38 +41,38 @@ module TTFunk
41
41
  attr_reader :max_context
42
42
 
43
43
  def tag
44
- "OS/2"
44
+ 'OS/2'
45
45
  end
46
46
 
47
47
  private
48
48
 
49
- def parse!
50
- @version = read(2, "n").first
49
+ def parse!
50
+ @version = read(2, 'n').first
51
51
 
52
- @ave_char_width = read_signed(1)
53
- @weight_class, @width_class = read(4, "nn")
54
- @type, @y_subscript_x_size, @y_subscript_y_size, @y_subscript_x_offset,
55
- @y_subscript_y_offset, @y_superscript_x_size, @y_superscript_y_size,
56
- @y_superscript_x_offset, @y_superscript_y_offset, @y_strikeout_size,
57
- @y_strikeout_position, @family_class = read_signed(12)
58
- @panose = io.read(10)
52
+ @ave_char_width = read_signed(1)
53
+ @weight_class, @width_class = read(4, 'nn')
54
+ @type, @y_subscript_x_size, @y_subscript_y_size, @y_subscript_x_offset,
55
+ @y_subscript_y_offset, @y_superscript_x_size, @y_superscript_y_size,
56
+ @y_superscript_x_offset, @y_superscript_y_offset, @y_strikeout_size,
57
+ @y_strikeout_position, @family_class = read_signed(12)
58
+ @panose = io.read(10)
59
59
 
60
- @char_range = io.read(16)
61
- @vendor_id = io.read(4)
60
+ @char_range = io.read(16)
61
+ @vendor_id = io.read(4)
62
62
 
63
- @selection, @first_char_index, @last_char_index = read(6, "n*")
63
+ @selection, @first_char_index, @last_char_index = read(6, 'n*')
64
64
 
65
- if @version > 0
66
- @ascent, @descent, @line_gap = read_signed(3)
67
- @win_ascent, @win_descent = read(4, "nn")
68
- @code_page_range = io.read(8)
65
+ if @version > 0
66
+ @ascent, @descent, @line_gap = read_signed(3)
67
+ @win_ascent, @win_descent = read(4, 'nn')
68
+ @code_page_range = io.read(8)
69
69
 
70
- if @version > 1
71
- @x_height, @cap_height = read_signed(2)
72
- @default_char, @break_char, @max_context = read(6, "nnn")
73
- end
70
+ if @version > 1
71
+ @x_height, @cap_height = read_signed(2)
72
+ @default_char, @break_char, @max_context = read(6, 'nnn')
74
73
  end
75
74
  end
75
+ end
76
76
  end
77
77
  end
78
78
  end