ttfunk 1.5.1 → 1.6.0

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