ttfunk 1.4.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
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