ttfunk 1.5.1 → 1.6.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 (90) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/CHANGELOG.md +60 -0
  5. data/README.md +2 -1
  6. data/lib/ttfunk.rb +45 -0
  7. data/lib/ttfunk/aggregate.rb +15 -0
  8. data/lib/ttfunk/bin_utils.rb +47 -0
  9. data/lib/ttfunk/bit_field.rb +31 -0
  10. data/lib/ttfunk/collection.rb +3 -1
  11. data/lib/ttfunk/directory.rb +6 -0
  12. data/lib/ttfunk/encoded_string.rb +97 -0
  13. data/lib/ttfunk/max.rb +25 -0
  14. data/lib/ttfunk/min.rb +25 -0
  15. data/lib/ttfunk/one_based_array.rb +36 -0
  16. data/lib/ttfunk/otf_encoder.rb +61 -0
  17. data/lib/ttfunk/placeholder.rb +13 -0
  18. data/lib/ttfunk/reader.rb +34 -32
  19. data/lib/ttfunk/resource_file.rb +7 -5
  20. data/lib/ttfunk/sci_form.rb +29 -0
  21. data/lib/ttfunk/sub_table.rb +38 -0
  22. data/lib/ttfunk/subset.rb +2 -0
  23. data/lib/ttfunk/subset/base.rb +61 -120
  24. data/lib/ttfunk/subset/code_page.rb +89 -0
  25. data/lib/ttfunk/subset/mac_roman.rb +5 -42
  26. data/lib/ttfunk/subset/unicode.rb +12 -6
  27. data/lib/ttfunk/subset/unicode_8bit.rb +14 -12
  28. data/lib/ttfunk/subset/windows_1252.rb +5 -47
  29. data/lib/ttfunk/subset_collection.rb +4 -0
  30. data/lib/ttfunk/sum.rb +20 -0
  31. data/lib/ttfunk/table.rb +4 -0
  32. data/lib/ttfunk/table/cff.rb +69 -0
  33. data/lib/ttfunk/table/cff/charset.rb +212 -0
  34. data/lib/ttfunk/table/cff/charsets.rb +14 -0
  35. data/lib/ttfunk/table/cff/charsets/expert.rb +189 -0
  36. data/lib/ttfunk/table/cff/charsets/expert_subset.rb +119 -0
  37. data/lib/ttfunk/table/cff/charsets/iso_adobe.rb +241 -0
  38. data/lib/ttfunk/table/cff/charsets/standard_strings.rb +404 -0
  39. data/lib/ttfunk/table/cff/charstring.rb +487 -0
  40. data/lib/ttfunk/table/cff/charstrings_index.rb +39 -0
  41. data/lib/ttfunk/table/cff/dict.rb +266 -0
  42. data/lib/ttfunk/table/cff/encoding.rb +220 -0
  43. data/lib/ttfunk/table/cff/encodings.rb +12 -0
  44. data/lib/ttfunk/table/cff/encodings/expert.rb +206 -0
  45. data/lib/ttfunk/table/cff/encodings/standard.rb +181 -0
  46. data/lib/ttfunk/table/cff/fd_selector.rb +150 -0
  47. data/lib/ttfunk/table/cff/font_dict.rb +79 -0
  48. data/lib/ttfunk/table/cff/font_index.rb +29 -0
  49. data/lib/ttfunk/table/cff/header.rb +33 -0
  50. data/lib/ttfunk/table/cff/index.rb +125 -0
  51. data/lib/ttfunk/table/cff/one_based_index.rb +31 -0
  52. data/lib/ttfunk/table/cff/path.rb +66 -0
  53. data/lib/ttfunk/table/cff/private_dict.rb +84 -0
  54. data/lib/ttfunk/table/cff/subr_index.rb +19 -0
  55. data/lib/ttfunk/table/cff/top_dict.rb +230 -0
  56. data/lib/ttfunk/table/cff/top_index.rb +16 -0
  57. data/lib/ttfunk/table/cmap.rb +4 -4
  58. data/lib/ttfunk/table/cmap/format00.rb +1 -2
  59. data/lib/ttfunk/table/cmap/format04.rb +11 -3
  60. data/lib/ttfunk/table/cmap/format06.rb +2 -0
  61. data/lib/ttfunk/table/cmap/format10.rb +2 -0
  62. data/lib/ttfunk/table/cmap/format12.rb +2 -0
  63. data/lib/ttfunk/table/cmap/subtable.rb +12 -8
  64. data/lib/ttfunk/table/dsig.rb +50 -0
  65. data/lib/ttfunk/table/glyf.rb +11 -9
  66. data/lib/ttfunk/table/glyf/compound.rb +14 -7
  67. data/lib/ttfunk/table/glyf/path_based.rb +47 -0
  68. data/lib/ttfunk/table/glyf/simple.rb +21 -15
  69. data/lib/ttfunk/table/head.rb +43 -5
  70. data/lib/ttfunk/table/hhea.rb +47 -4
  71. data/lib/ttfunk/table/hmtx.rb +11 -4
  72. data/lib/ttfunk/table/kern.rb +3 -0
  73. data/lib/ttfunk/table/kern/format0.rb +3 -0
  74. data/lib/ttfunk/table/loca.rb +2 -0
  75. data/lib/ttfunk/table/maxp.rb +144 -10
  76. data/lib/ttfunk/table/name.rb +75 -37
  77. data/lib/ttfunk/table/os2.rb +327 -4
  78. data/lib/ttfunk/table/post.rb +8 -1
  79. data/lib/ttfunk/table/post/format10.rb +2 -0
  80. data/lib/ttfunk/table/post/format20.rb +5 -1
  81. data/lib/ttfunk/table/post/format30.rb +2 -0
  82. data/lib/ttfunk/table/post/format40.rb +2 -0
  83. data/lib/ttfunk/table/sbix.rb +2 -0
  84. data/lib/ttfunk/table/simple.rb +2 -0
  85. data/lib/ttfunk/table/vorg.rb +54 -0
  86. data/lib/ttfunk/ttf_encoder.rb +220 -0
  87. metadata +88 -20
  88. metadata.gz.sig +0 -0
  89. data/lib/ttfunk/encoding/mac_roman.rb +0 -100
  90. data/lib/ttfunk/encoding/windows_1252.rb +0 -76
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTFunk
4
+ class Table
5
+ class Cff < TTFunk::Table
6
+ class FontDict < TTFunk::Table::Cff::Dict
7
+ PLACEHOLDER_LENGTH = 5
8
+ OPERATORS = { private: 18 }.freeze
9
+ OPERATOR_CODES = OPERATORS.invert
10
+
11
+ attr_reader :top_dict
12
+
13
+ def initialize(top_dict, file, offset, length = nil)
14
+ @top_dict = top_dict
15
+ super(file, offset, length)
16
+ end
17
+
18
+ def encode(_mapping)
19
+ EncodedString.new do |result|
20
+ each do |operator, operands|
21
+ case OPERATOR_CODES[operator]
22
+ when :private
23
+ result << encode_private
24
+ else
25
+ operands.each { |operand| result << encode_operand(operand) }
26
+ end
27
+
28
+ result << encode_operator(operator)
29
+ end
30
+ end
31
+ end
32
+
33
+ def finalize(new_cff_data, mapping)
34
+ encoded_private_dict = private_dict.encode(mapping)
35
+ encoded_offset = encode_integer32(new_cff_data.length)
36
+ encoded_length = encode_integer32(encoded_private_dict.length)
37
+
38
+ new_cff_data.resolve_placeholder(
39
+ :"private_length_#{@table_offset}", encoded_length
40
+ )
41
+
42
+ new_cff_data.resolve_placeholder(
43
+ :"private_offset_#{@table_offset}", encoded_offset
44
+ )
45
+
46
+ private_dict.finalize(encoded_private_dict)
47
+ new_cff_data << encoded_private_dict
48
+ end
49
+
50
+ def private_dict
51
+ @private_dict ||=
52
+ if (info = self[OPERATORS[:private]])
53
+ private_dict_length, private_dict_offset = info
54
+
55
+ PrivateDict.new(
56
+ file,
57
+ top_dict.cff_offset + private_dict_offset,
58
+ private_dict_length
59
+ )
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def encode_private
66
+ EncodedString.new do |result|
67
+ result << Placeholder.new(
68
+ :"private_length_#{@table_offset}", length: PLACEHOLDER_LENGTH
69
+ )
70
+
71
+ result << Placeholder.new(
72
+ :"private_offset_#{@table_offset}", length: PLACEHOLDER_LENGTH
73
+ )
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTFunk
4
+ class Table
5
+ class Cff < TTFunk::Table
6
+ class FontIndex < TTFunk::Table::Cff::Index
7
+ attr_reader :top_dict
8
+
9
+ def initialize(top_dict, file, offset, length = nil)
10
+ super(file, offset, length)
11
+ @top_dict = top_dict
12
+ end
13
+
14
+ def [](index)
15
+ entry_cache[index] ||= begin
16
+ start, finish = absolute_offsets_for(index)
17
+ TTFunk::Table::Cff::FontDict.new(
18
+ top_dict, file, start, (finish - start) + 1
19
+ )
20
+ end
21
+ end
22
+
23
+ def finalize(new_cff_data, mapping)
24
+ each { |font_dict| font_dict.finalize(new_cff_data, mapping) }
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTFunk
4
+ class Table
5
+ class Cff < TTFunk::Table
6
+ class Header < TTFunk::SubTable
7
+ # cff format version numbers
8
+ attr_reader :major
9
+ attr_reader :minor
10
+
11
+ # size of the header itself
12
+ attr_reader :header_size
13
+
14
+ # size of all offsets from beginning of table
15
+ attr_reader :absolute_offset_size
16
+
17
+ def length
18
+ 4
19
+ end
20
+
21
+ def encode
22
+ [major, minor, header_size, absolute_offset_size].pack('C*')
23
+ end
24
+
25
+ private
26
+
27
+ def parse!
28
+ @major, @minor, @header_size, @absolute_offset_size = read(4, 'C*')
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTFunk
4
+ class Table
5
+ class Cff < TTFunk::Table
6
+ class Index < TTFunk::SubTable
7
+ include Enumerable
8
+
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
+
18
+ def [](index)
19
+ entry_cache[index] ||= raw_data[
20
+ offsets[index]...offsets[index + 1]
21
+ ]
22
+ end
23
+
24
+ def each
25
+ return to_enum(__method__) unless block_given?
26
+
27
+ count.times { |i| yield self[i] }
28
+ end
29
+
30
+ def encode
31
+ result = EncodedString.new
32
+
33
+ entries = each_with_object([]).with_index do |(entry, ret), index|
34
+ new_entry = block_given? ? yield(entry, index) : entry
35
+ ret << new_entry if new_entry
36
+ end
37
+
38
+ # "An empty INDEX is represented by a count field with a 0 value and
39
+ # no additional fields. Thus, the total size of an empty INDEX is 2
40
+ # bytes."
41
+ result << [entries.size].pack('n')
42
+ return result if entries.empty?
43
+
44
+ offset_size = (Math.log2(entries.size) / 8.0).round + 1
45
+ result << [offset_size].pack('C')
46
+ data_offset = 1
47
+
48
+ data = EncodedString.new
49
+
50
+ entries.each do |entry|
51
+ result << encode_offset(data_offset, offset_size)
52
+ data << entry
53
+ data_offset += entry.length
54
+ end
55
+
56
+ unless entries.empty?
57
+ result << encode_offset(data_offset, offset_size)
58
+ end
59
+
60
+ result << data
61
+ end
62
+
63
+ private
64
+
65
+ def entry_cache
66
+ @entry_cache ||= {}
67
+ end
68
+
69
+ def absolute_offsets_for(index)
70
+ [
71
+ table_offset + offsets[index] + data_start_pos,
72
+ table_offset + offsets[index + 1] + data_start_pos
73
+ ]
74
+ end
75
+
76
+ def encode_offset(offset, offset_size)
77
+ case offset_size
78
+ when 1
79
+ [offset].pack('C')
80
+ when 2
81
+ [offset].pack('n')
82
+ when 3
83
+ [offset].pack('N')[1..-1]
84
+ when 4
85
+ [offset].pack('N')
86
+ end
87
+ end
88
+
89
+ def parse!
90
+ @count = read(2, 'n').first
91
+
92
+ if count == 0
93
+ @length = 2
94
+ @data = []
95
+ return
96
+ end
97
+
98
+ @offset_size = read(1, 'C').first
99
+
100
+ # read an extra offset_size bytes to get rid of the first offset,
101
+ # which is always 1
102
+ io.read(offset_size)
103
+
104
+ @raw_offset_length = count * offset_size
105
+ raw_offsets = io.read(raw_offset_length)
106
+
107
+ @offsets = [0] + Array.new(count) do |idx|
108
+ start = offset_size * idx
109
+ finish = offset_size * (idx + 1)
110
+ unpack_offset(raw_offsets[start...finish]) - 1
111
+ end
112
+
113
+ @raw_data = io.read(offsets.last)
114
+ @data_start_pos = 3 + offset_size + raw_offset_length
115
+ @length = data_start_pos + raw_data.size
116
+ end
117
+
118
+ def unpack_offset(offset_data)
119
+ padding = "\x00" * (4 - offset_size)
120
+ (padding + offset_data).unpack1('N')
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module TTFunk
6
+ class Table
7
+ class Cff < TTFunk::Table
8
+ class OneBasedIndex
9
+ extend Forwardable
10
+
11
+ def_delegators :base_index, :each, :table_offset,
12
+ :count, :length, :encode
13
+
14
+ attr_reader :base_index
15
+
16
+ def initialize(*args)
17
+ @base_index = Index.new(*args)
18
+ end
19
+
20
+ def [](idx)
21
+ if idx == 0
22
+ raise IndexError,
23
+ "index #{idx} was outside the bounds of the index"
24
+ end
25
+
26
+ base_index[idx - 1]
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTFunk
4
+ class Table
5
+ class Cff < TTFunk::Table
6
+ class Path
7
+ CLOSE_PATH_CMD = [:close].freeze
8
+
9
+ attr_reader :commands, :number_of_contours
10
+
11
+ def initialize
12
+ @commands = []
13
+ @number_of_contours = 0
14
+ end
15
+
16
+ def move_to(x, y)
17
+ @commands << [:move, x, y]
18
+ end
19
+
20
+ def line_to(x, y)
21
+ @commands << [:line, x, y]
22
+ end
23
+
24
+ def curve_to(x1, y1, x2, y2, x, y)
25
+ @commands << [:curve, x1, y1, x2, y2, x, y]
26
+ end
27
+
28
+ def close_path
29
+ @commands << CLOSE_PATH_CMD
30
+ @number_of_contours += 1
31
+ end
32
+
33
+ def render(x: 0, y: 0, font_size: 72, units_per_em: 1000)
34
+ new_path = self.class.new
35
+ scale = 1.0 / units_per_em * font_size
36
+
37
+ commands.each do |cmd|
38
+ case cmd[:type]
39
+ when :move
40
+ new_path.move_to(x + (cmd[1] * scale), y + (-cmd[2] * scale))
41
+ when :line
42
+ new_path.line_to(x + (cmd[1] * scale), y + (-cmd[2] * scale))
43
+ when :curve
44
+ new_path.curve_to(
45
+ x + (cmd[1] * scale),
46
+ y + (-cmd[2] * scale),
47
+ x + (cmd[3] * scale), y + (-cmd[4] * scale),
48
+ x + (cmd[5] * scale), y + (-cmd[6] * scale)
49
+ )
50
+ when :close
51
+ new_path.close_path
52
+ end
53
+ end
54
+
55
+ new_path
56
+ end
57
+
58
+ private
59
+
60
+ def format_values(command)
61
+ command[1..-1].map { |k| format('%.2f', k) }.join(' ')
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTFunk
4
+ class Table
5
+ class Cff < TTFunk::Table
6
+ class PrivateDict < TTFunk::Table::Cff::Dict
7
+ DEFAULT_WIDTH_X_DEFAULT = 0
8
+ DEFAULT_WIDTH_X_NOMINAL = 0
9
+ PLACEHOLDER_LENGTH = 5
10
+
11
+ OPERATORS = {
12
+ subrs: 19,
13
+ default_width_x: 20,
14
+ nominal_width_x: 21
15
+ }.freeze
16
+
17
+ OPERATOR_CODES = OPERATORS.invert
18
+
19
+ # @TODO: use mapping to determine which subroutines are still used.
20
+ # For now, just encode them all.
21
+ def encode(_mapping)
22
+ EncodedString.new do |result|
23
+ each do |operator, operands|
24
+ case OPERATOR_CODES[operator]
25
+ when :subrs
26
+ result << encode_subrs
27
+ else
28
+ operands.each { |operand| result << encode_operand(operand) }
29
+ end
30
+
31
+ result << encode_operator(operator)
32
+ end
33
+ end
34
+ end
35
+
36
+ def finalize(private_dict_data)
37
+ return unless subr_index
38
+
39
+ encoded_subr_index = subr_index.encode
40
+ encoded_offset = encode_integer32(private_dict_data.length)
41
+
42
+ private_dict_data.resolve_placeholder(
43
+ :"subrs_#{@table_offset}", encoded_offset
44
+ )
45
+
46
+ private_dict_data << encoded_subr_index
47
+ end
48
+
49
+ def subr_index
50
+ @subr_index ||=
51
+ if (subr_offset = self[OPERATORS[:subrs]])
52
+ SubrIndex.new(file, table_offset + subr_offset.first)
53
+ end
54
+ end
55
+
56
+ def default_width_x
57
+ if (width = self[OPERATORS[:default_width_x]])
58
+ width.first
59
+ else
60
+ DEFAULT_WIDTH_X_DEFAULT
61
+ end
62
+ end
63
+
64
+ def nominal_width_x
65
+ if (width = self[OPERATORS[:nominal_width_x]])
66
+ width.first
67
+ else
68
+ DEFAULT_WIDTH_X_NOMINAL
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def encode_subrs
75
+ EncodedString.new.tap do |result|
76
+ result << Placeholder.new(
77
+ :"subrs_#{@table_offset}", length: PLACEHOLDER_LENGTH
78
+ )
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end