ttfunk 1.7.0 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +74 -0
  4. data/README.md +17 -15
  5. data/lib/ttfunk/aggregate.rb +5 -0
  6. data/lib/ttfunk/bin_utils.rb +27 -8
  7. data/lib/ttfunk/bit_field.rb +25 -2
  8. data/lib/ttfunk/collection.rb +27 -3
  9. data/lib/ttfunk/directory.rb +7 -1
  10. data/lib/ttfunk/encoded_string.rb +58 -4
  11. data/lib/ttfunk/max.rb +14 -0
  12. data/lib/ttfunk/min.rb +14 -0
  13. data/lib/ttfunk/one_based_array.rb +20 -0
  14. data/lib/ttfunk/otf_encoder.rb +5 -14
  15. data/lib/ttfunk/placeholder.rb +15 -1
  16. data/lib/ttfunk/reader.rb +6 -4
  17. data/lib/ttfunk/resource_file.rb +29 -5
  18. data/lib/ttfunk/sci_form.rb +20 -3
  19. data/lib/ttfunk/sub_table.rb +29 -4
  20. data/lib/ttfunk/subset/base.rb +48 -0
  21. data/lib/ttfunk/subset/code_page.rb +49 -2
  22. data/lib/ttfunk/subset/mac_roman.rb +2 -0
  23. data/lib/ttfunk/subset/unicode.rb +32 -0
  24. data/lib/ttfunk/subset/unicode_8bit.rb +32 -0
  25. data/lib/ttfunk/subset/windows_1252.rb +2 -0
  26. data/lib/ttfunk/subset.rb +8 -0
  27. data/lib/ttfunk/subset_collection.rb +39 -14
  28. data/lib/ttfunk/sum.rb +13 -0
  29. data/lib/ttfunk/table/cff/charset.rb +96 -18
  30. data/lib/ttfunk/table/cff/charsets/expert.rb +3 -2
  31. data/lib/ttfunk/table/cff/charsets/expert_subset.rb +3 -2
  32. data/lib/ttfunk/table/cff/charsets/iso_adobe.rb +3 -2
  33. data/lib/ttfunk/table/cff/charsets/standard_strings.rb +3 -2
  34. data/lib/ttfunk/table/cff/charsets.rb +1 -0
  35. data/lib/ttfunk/table/cff/charstring.rb +33 -12
  36. data/lib/ttfunk/table/cff/charstrings_index.rb +17 -11
  37. data/lib/ttfunk/table/cff/dict.rb +53 -23
  38. data/lib/ttfunk/table/cff/encoding.rb +82 -24
  39. data/lib/ttfunk/table/cff/encodings/expert.rb +3 -2
  40. data/lib/ttfunk/table/cff/encodings/standard.rb +3 -2
  41. data/lib/ttfunk/table/cff/encodings.rb +1 -0
  42. data/lib/ttfunk/table/cff/fd_selector.rb +61 -21
  43. data/lib/ttfunk/table/cff/font_dict.rb +30 -18
  44. data/lib/ttfunk/table/cff/font_index.rb +22 -10
  45. data/lib/ttfunk/table/cff/header.rb +16 -3
  46. data/lib/ttfunk/table/cff/index.rb +97 -65
  47. data/lib/ttfunk/table/cff/one_based_index.rb +11 -1
  48. data/lib/ttfunk/table/cff/path.rb +43 -4
  49. data/lib/ttfunk/table/cff/private_dict.rb +31 -11
  50. data/lib/ttfunk/table/cff/subr_index.rb +7 -2
  51. data/lib/ttfunk/table/cff/top_dict.rb +82 -59
  52. data/lib/ttfunk/table/cff/top_index.rb +10 -6
  53. data/lib/ttfunk/table/cff.rb +41 -21
  54. data/lib/ttfunk/table/cmap/format00.rb +27 -6
  55. data/lib/ttfunk/table/cmap/format04.rb +34 -14
  56. data/lib/ttfunk/table/cmap/format06.rb +28 -1
  57. data/lib/ttfunk/table/cmap/format10.rb +29 -2
  58. data/lib/ttfunk/table/cmap/format12.rb +29 -2
  59. data/lib/ttfunk/table/cmap/subtable.rb +50 -6
  60. data/lib/ttfunk/table/cmap.rb +21 -0
  61. data/lib/ttfunk/table/dsig.rb +47 -6
  62. data/lib/ttfunk/table/glyf/compound.rb +73 -6
  63. data/lib/ttfunk/table/glyf/path_based.rb +40 -3
  64. data/lib/ttfunk/table/glyf/simple.rb +50 -5
  65. data/lib/ttfunk/table/glyf.rb +15 -7
  66. data/lib/ttfunk/table/head.rb +84 -6
  67. data/lib/ttfunk/table/hhea.rb +71 -10
  68. data/lib/ttfunk/table/hmtx.rb +32 -5
  69. data/lib/ttfunk/table/kern/format0.rb +25 -7
  70. data/lib/ttfunk/table/kern.rb +16 -4
  71. data/lib/ttfunk/table/loca.rb +21 -8
  72. data/lib/ttfunk/table/maxp.rb +195 -10
  73. data/lib/ttfunk/table/name.rb +126 -9
  74. data/lib/ttfunk/table/os2.rb +150 -26
  75. data/lib/ttfunk/table/post/format10.rb +7 -0
  76. data/lib/ttfunk/table/post/format20.rb +9 -0
  77. data/lib/ttfunk/table/post/format30.rb +6 -0
  78. data/lib/ttfunk/table/post/format40.rb +5 -0
  79. data/lib/ttfunk/table/post.rb +63 -7
  80. data/lib/ttfunk/table/sbix.rb +50 -14
  81. data/lib/ttfunk/table/simple.rb +5 -0
  82. data/lib/ttfunk/table/vorg.rb +31 -3
  83. data/lib/ttfunk/table.rb +20 -1
  84. data/lib/ttfunk/ttf_encoder.rb +39 -41
  85. data/lib/ttfunk.rb +154 -1
  86. data.tar.gz.sig +0 -0
  87. metadata +50 -28
  88. metadata.gz.sig +0 -0
@@ -3,26 +3,38 @@
3
3
  module TTFunk
4
4
  class Table
5
5
  class Cff < TTFunk::Table
6
+ # CFF Font Dict Index.
6
7
  class FontIndex < TTFunk::Table::Cff::Index
8
+ # Top dict.
9
+ # @return [TTFunk::Table::Cff::TopDict]
7
10
  attr_reader :top_dict
8
11
 
12
+ # @param top_dict [TTFunk::Table:Cff::TopDict]
13
+ # @param file [TTFunk::File]
14
+ # @param offset [Integer]
15
+ # @param length [Integer]
9
16
  def initialize(top_dict, file, offset, length = nil)
10
17
  super(file, offset, length)
11
18
  @top_dict = top_dict
12
19
  end
13
20
 
14
- def [](index)
15
- entry_cache[index] ||=
16
- begin
17
- start, finish = absolute_offsets_for(index)
18
- TTFunk::Table::Cff::FontDict.new(
19
- top_dict, file, start, (finish - start) + 1
20
- )
21
- end
21
+ # Finalize index.
22
+ #
23
+ # @param new_cff_data [TTFunk::EncodedString]
24
+ # @return [void]
25
+ def finalize(new_cff_data)
26
+ each { |font_dict| font_dict.finalize(new_cff_data) }
22
27
  end
23
28
 
24
- def finalize(new_cff_data, mapping)
25
- each { |font_dict| font_dict.finalize(new_cff_data, mapping) }
29
+ private
30
+
31
+ def decode_item(_index, offset, length)
32
+ TTFunk::Table::Cff::FontDict.new(top_dict, file, offset, length)
33
+ end
34
+
35
+ def encode_items(*)
36
+ # Re-encode font dicts
37
+ map(&:encode)
26
38
  end
27
39
  end
28
40
  end
@@ -3,21 +3,34 @@
3
3
  module TTFunk
4
4
  class Table
5
5
  class Cff < TTFunk::Table
6
+ # CFF Header.
6
7
  class Header < TTFunk::SubTable
7
- # cff format version numbers
8
+ # CFF table major version.
9
+ # @return [Integer]
8
10
  attr_reader :major
11
+
12
+ # CFF table minor version.
13
+ # @return [Integer]
9
14
  attr_reader :minor
10
15
 
11
- # size of the header itself
16
+ # Size of the header itself.
17
+ # @return [Integer]
12
18
  attr_reader :header_size
13
19
 
14
- # size of all offsets from beginning of table
20
+ # Size of all offsets from beginning of table.
21
+ # @return [Integer]
15
22
  attr_reader :absolute_offset_size
16
23
 
24
+ # Length of header.
25
+ #
26
+ # @return [Integer]
17
27
  def length
18
28
  4
19
29
  end
20
30
 
31
+ # Encode header.
32
+ #
33
+ # @return [String]
21
34
  def encode
22
35
  [major, minor, header_size, absolute_offset_size].pack('C*')
23
36
  end
@@ -3,75 +3,104 @@
3
3
  module TTFunk
4
4
  class Table
5
5
  class Cff < TTFunk::Table
6
+ # CFF Index.
6
7
  class Index < TTFunk::SubTable
7
8
  include Enumerable
8
9
 
9
- # number of objects in the index
10
- attr_reader :count
11
-
12
- # offset array element size
13
- attr_reader :offset_size
14
-
15
- attr_reader :raw_offset_length, :offsets, :raw_data
16
- attr_reader :data_start_pos
17
-
10
+ # Get value by index.
11
+ #
12
+ # @param index [Integer]
13
+ # @return [any]
18
14
  def [](index)
19
- entry_cache[index] ||= raw_data[
20
- offsets[index]...offsets[index + 1]
21
- ]
15
+ return if index >= items_count
16
+
17
+ entry_cache[index] ||=
18
+ decode_item(
19
+ index,
20
+ data_reference_offset + offsets[index],
21
+ offsets[index + 1] - offsets[index],
22
+ )
22
23
  end
23
24
 
24
- def each
25
- return to_enum(__method__) unless block_given?
25
+ # Iterate over index items.
26
+ #
27
+ # @overload each()
28
+ # @yieldparam item [any]
29
+ # @return [void]
30
+ # @overload each()
31
+ # @return [Enumerator]
32
+ def each(&block)
33
+ return to_enum(__method__) unless block
34
+
35
+ items_count.times do |i|
36
+ yield(self[i])
37
+ end
38
+ end
26
39
 
27
- count.times { |i| yield self[i] }
40
+ # Numer of items in this index.
41
+ #
42
+ # @return [Integer]
43
+ def items_count
44
+ items.length
28
45
  end
29
46
 
30
- def encode
31
- result = EncodedString.new
47
+ # Encode index.
48
+ #
49
+ # @param args all arguments are passed to `encode_item` method.
50
+ # @return [TTFunk::EncodedString]
51
+ def encode(*args)
52
+ new_items = encode_items(*args)
32
53
 
33
- entries =
34
- each_with_object([]).with_index do |(entry, ret), index|
35
- new_entry = block_given? ? yield(entry, index) : entry
36
- ret << new_entry if new_entry
37
- end
54
+ if new_items.empty?
55
+ return [0].pack('n')
56
+ end
38
57
 
39
- # "An empty INDEX is represented by a count field with a 0 value and
40
- # no additional fields. Thus, the total size of an empty INDEX is 2
41
- # bytes."
42
- result << [entries.size].pack('n')
43
- return result if entries.empty?
58
+ if new_items.length > 0xffff
59
+ raise Error, 'Too many items in a CFF index'
60
+ end
44
61
 
45
- offset_size = (Math.log2(entries.size) / 8.0).round + 1
46
- result << [offset_size].pack('C')
47
- data_offset = 1
62
+ offsets_array =
63
+ new_items
64
+ .each_with_object([1]) { |item, offsets|
65
+ offsets << (offsets.last + item.length)
66
+ }
48
67
 
49
- data = EncodedString.new
68
+ offset_size = (offsets_array.last.bit_length / 8.0).ceil
50
69
 
51
- entries.each do |entry|
52
- result << encode_offset(data_offset, offset_size)
53
- data << entry
54
- data_offset += entry.length
55
- end
70
+ offsets_array.map! { |offset| encode_offset(offset, offset_size) }
56
71
 
57
- unless entries.empty?
58
- result << encode_offset(data_offset, offset_size)
59
- end
60
-
61
- result << data
72
+ EncodedString.new.concat(
73
+ [new_items.length, offset_size].pack('nC'),
74
+ *offsets_array,
75
+ *new_items,
76
+ )
62
77
  end
63
78
 
64
79
  private
65
80
 
81
+ attr_reader :items
82
+ attr_reader :offsets
83
+ attr_reader :data_reference_offset
84
+
66
85
  def entry_cache
67
86
  @entry_cache ||= {}
68
87
  end
69
88
 
70
- def absolute_offsets_for(index)
71
- [
72
- table_offset + offsets[index] + data_start_pos,
73
- table_offset + offsets[index + 1] + data_start_pos
74
- ]
89
+ # Returns an array of EncodedString elements (plain strings,
90
+ # placeholders, or EncodedString instances). Each element is supposed to
91
+ # represent an encoded item.
92
+ #
93
+ # This is the place to do all the filtering, reordering, or individual
94
+ # item encoding.
95
+ #
96
+ # It gets all the arguments `encode` gets.
97
+ def encode_items(*)
98
+ items
99
+ end
100
+
101
+ # By default do nothing
102
+ def decode_item(index, _offset, _length)
103
+ items[index]
75
104
  end
76
105
 
77
106
  def encode_offset(offset, offset_size)
@@ -81,42 +110,45 @@ module TTFunk
81
110
  when 2
82
111
  [offset].pack('n')
83
112
  when 3
84
- [offset].pack('N')[1..-1]
113
+ [offset].pack('N')[1..]
85
114
  when 4
86
115
  [offset].pack('N')
87
116
  end
88
117
  end
89
118
 
90
119
  def parse!
91
- @count = read(2, 'n').first
120
+ @entry_cache = {}
121
+
122
+ num_entries = read(2, 'n').first
92
123
 
93
- if count.zero?
124
+ if num_entries.zero?
94
125
  @length = 2
95
- @data = []
126
+ @items = []
96
127
  return
97
128
  end
98
129
 
99
- @offset_size = read(1, 'C').first
130
+ offset_size = read(1, 'C').first
100
131
 
101
- # read an extra offset_size bytes to get rid of the first offset,
102
- # which is always 1
103
- io.read(offset_size)
132
+ @offsets =
133
+ Array.new(num_entries + 1) {
134
+ unpack_offset(io.read(offset_size), offset_size)
135
+ }
104
136
 
105
- @raw_offset_length = count * offset_size
106
- raw_offsets = io.read(raw_offset_length)
137
+ @data_reference_offset = table_offset + 3 + (offsets.length * offset_size) - 1
107
138
 
108
- @offsets = [0] + Array.new(count) do |idx|
109
- start = offset_size * idx
110
- finish = offset_size * (idx + 1)
111
- unpack_offset(raw_offsets[start...finish]) - 1
112
- end
139
+ @length =
140
+ 2 + # num entries
141
+ 1 + # offset size
142
+ (offsets.length * offset_size) + # offsets
143
+ offsets.last - 1 # items
113
144
 
114
- @raw_data = io.read(offsets.last)
115
- @data_start_pos = 3 + offset_size + raw_offset_length
116
- @length = data_start_pos + raw_data.size
145
+ @items =
146
+ offsets.each_cons(2).map { |offset, next_offset|
147
+ io.read(next_offset - offset)
148
+ }
117
149
  end
118
150
 
119
- def unpack_offset(offset_data)
151
+ def unpack_offset(offset_data, offset_size)
120
152
  padding = "\x00" * (4 - offset_size)
121
153
  (padding + offset_data).unpack1('N')
122
154
  end
@@ -5,22 +5,32 @@ require 'forwardable'
5
5
  module TTFunk
6
6
  class Table
7
7
  class Cff < TTFunk::Table
8
+ # CFF Index with indexing starting at 1.
8
9
  class OneBasedIndex
9
10
  extend Forwardable
10
11
 
11
12
  def_delegators :base_index,
12
13
  :each,
13
14
  :table_offset,
14
- :count,
15
+ :items_count,
15
16
  :length,
16
17
  :encode
17
18
 
19
+ # Underlaying Index.
20
+ # @return [TTFunk::Table::Cff::Index]
18
21
  attr_reader :base_index
19
22
 
23
+ # @param args [Array] all params are passed to the base index.
24
+ # @see Index
20
25
  def initialize(*args)
21
26
  @base_index = Index.new(*args)
22
27
  end
23
28
 
29
+ # Get item by index.
30
+ #
31
+ # @param idx [Integer]
32
+ # @return [any]
33
+ # @raise [IndexError] when requested index is 0.
24
34
  def [](idx)
25
35
  if idx.zero?
26
36
  raise IndexError,
@@ -3,33 +3,72 @@
3
3
  module TTFunk
4
4
  class Table
5
5
  class Cff < TTFunk::Table
6
+ # Path. Mostly used for CFF glyph outlines.
6
7
  class Path
8
+ # Close path command.
7
9
  CLOSE_PATH_CMD = [:close].freeze
8
10
 
9
- attr_reader :commands, :number_of_contours
11
+ # Commands in this path.
12
+ # @return [Array]
13
+ attr_reader :commands
14
+
15
+ # Number of contours in this path.
16
+ # @return [Integer]
17
+ attr_reader :number_of_contours
10
18
 
11
19
  def initialize
12
20
  @commands = []
13
21
  @number_of_contours = 0
14
22
  end
15
23
 
24
+ # Move implicit cursor to coordinates.
25
+ #
26
+ # @param x [Integer, Float]
27
+ # @param y [Integer, Float]
28
+ # @return [void]
16
29
  def move_to(x, y)
17
30
  @commands << [:move, x, y]
18
31
  end
19
32
 
33
+ # Add a line to coordinates.
34
+ #
35
+ # @param x [Integer, Float]
36
+ # @param y [Integer, Float]
37
+ # @return [void]
20
38
  def line_to(x, y)
21
39
  @commands << [:line, x, y]
22
40
  end
23
41
 
24
- def curve_to(x1, y1, x2, y2, x, y) # rubocop: disable Metrics/ParameterLists,Style/CommentedKeyword
42
+ # Add a Bézier curve. Current position is the first control point, (`x1`,
43
+ # `y1`) is the second, (`x2`, `y2`) is the third, and (`x`, `y`) is the
44
+ # last control point.
45
+ #
46
+ # @param x1 [Integer, Float]
47
+ # @param y1 [Integer, Float]
48
+ # @param x2 [Integer, Float]
49
+ # @param y2 [Integer, Float]
50
+ # @param x [Integer, Float]
51
+ # @param y [Integer, Float]
52
+ # @return [void]
53
+ def curve_to(x1, y1, x2, y2, x, y) # rubocop: disable Metrics/ParameterLists
25
54
  @commands << [:curve, x1, y1, x2, y2, x, y]
26
55
  end
27
56
 
57
+ # Close current contour.
58
+ #
59
+ # @return [void]
28
60
  def close_path
29
61
  @commands << CLOSE_PATH_CMD
30
62
  @number_of_contours += 1
31
63
  end
32
64
 
65
+ # Reposition and scale path.
66
+ #
67
+ # @param x [Integer, Float] new horizontal position.
68
+ # @param y [Integer, Float] new vertical position.
69
+ # @param font_size [Integer, Float] font size.
70
+ # @param units_per_em [Integer] units per Em as defined in the font.
71
+ # @return [TTFunk::Table::Cff::Path]
33
72
  def render(x: 0, y: 0, font_size: 72, units_per_em: 1000)
34
73
  new_path = self.class.new
35
74
  scale = 1.0 / units_per_em * font_size
@@ -47,7 +86,7 @@ module TTFunk
47
86
  x + (cmd[3] * scale),
48
87
  y + (-cmd[4] * scale),
49
88
  x + (cmd[5] * scale),
50
- y + (-cmd[6] * scale)
89
+ y + (-cmd[6] * scale),
51
90
  )
52
91
  when :close
53
92
  new_path.close_path
@@ -60,7 +99,7 @@ module TTFunk
60
99
  private
61
100
 
62
101
  def format_values(command)
63
- command[1..-1].map { |k| format('%.2f', k) }.join(' ')
102
+ command[1..].map { |k| format('%.2f', k) }.join(' ')
64
103
  end
65
104
  end
66
105
  end
@@ -3,22 +3,33 @@
3
3
  module TTFunk
4
4
  class Table
5
5
  class Cff < TTFunk::Table
6
+ # CFF Private dict.
6
7
  class PrivateDict < TTFunk::Table::Cff::Dict
8
+ # Default value of Default Width X.
7
9
  DEFAULT_WIDTH_X_DEFAULT = 0
10
+
11
+ # Default value of Nominal Width X.
8
12
  DEFAULT_WIDTH_X_NOMINAL = 0
13
+
14
+ # Length of placeholders.
9
15
  PLACEHOLDER_LENGTH = 5
10
16
 
17
+ # Operators we care about in this dict.
11
18
  OPERATORS = {
12
19
  subrs: 19,
13
20
  default_width_x: 20,
14
- nominal_width_x: 21
21
+ nominal_width_x: 21,
15
22
  }.freeze
16
23
 
24
+ # Inverse operator mapping.
17
25
  OPERATOR_CODES = OPERATORS.invert
18
26
 
19
- # @TODO: use mapping to determine which subroutines are still used.
20
- # For now, just encode them all.
21
- def encode(_mapping)
27
+ # Encode dict.
28
+ #
29
+ # @return [TTFunk::EncodedString]
30
+ def encode
31
+ # TODO: use mapping to determine which subroutines are still used.
32
+ # For now, just encode them all.
22
33
  EncodedString.new do |result|
23
34
  each do |operator, operands|
24
35
  case OPERATOR_CODES[operator]
@@ -33,19 +44,24 @@ module TTFunk
33
44
  end
34
45
  end
35
46
 
47
+ # Finalize dict.
48
+ #
49
+ # @param private_dict_data [TTFunk::EncodedString]
50
+ # @return [void]
36
51
  def finalize(private_dict_data)
37
52
  return unless subr_index
38
53
 
39
54
  encoded_subr_index = subr_index.encode
40
55
  encoded_offset = encode_integer32(private_dict_data.length)
41
56
 
42
- private_dict_data.resolve_placeholder(
43
- :"subrs_#{@table_offset}", encoded_offset
44
- )
57
+ private_dict_data.resolve_placeholder(:"subrs_#{@table_offset}", encoded_offset)
45
58
 
46
59
  private_dict_data << encoded_subr_index
47
60
  end
48
61
 
62
+ # Subroutine index.
63
+ #
64
+ # @return [TTFunk::Table::Cff::SubrIndex, nil]
49
65
  def subr_index
50
66
  @subr_index ||=
51
67
  if (subr_offset = self[OPERATORS[:subrs]])
@@ -53,6 +69,9 @@ module TTFunk
53
69
  end
54
70
  end
55
71
 
72
+ # Default Width X.
73
+ #
74
+ # @return [Integer]
56
75
  def default_width_x
57
76
  if (width = self[OPERATORS[:default_width_x]])
58
77
  width.first
@@ -61,6 +80,9 @@ module TTFunk
61
80
  end
62
81
  end
63
82
 
83
+ # Nominal Width X.
84
+ #
85
+ # @return [Integer]
64
86
  def nominal_width_x
65
87
  if (width = self[OPERATORS[:nominal_width_x]])
66
88
  width.first
@@ -72,10 +94,8 @@ module TTFunk
72
94
  private
73
95
 
74
96
  def encode_subrs
75
- EncodedString.new.tap do |result|
76
- result << Placeholder.new(
77
- :"subrs_#{@table_offset}", length: PLACEHOLDER_LENGTH
78
- )
97
+ EncodedString.new do |result|
98
+ result << Placeholder.new(:"subrs_#{@table_offset}", length: PLACEHOLDER_LENGTH)
79
99
  end
80
100
  end
81
101
  end
@@ -3,11 +3,16 @@
3
3
  module TTFunk
4
4
  class Table
5
5
  class Cff < TTFunk::Table
6
+ # CFF Subroutine index.
6
7
  class SubrIndex < TTFunk::Table::Cff::Index
8
+ # Subroutine index biase. For correct subroutine selection the
9
+ # calculated bias must be added to the subroutine number operand before
10
+ # accessing the index.
11
+ # @return [Integer]
7
12
  def bias
8
- if count < 1240
13
+ if items.length < 1240
9
14
  107
10
- elsif count < 33_900
15
+ elsif items.length < 33_900
11
16
  1131
12
17
  else
13
18
  32_768