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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../../reader'
2
4
 
3
5
  module TTFunk
@@ -13,18 +15,22 @@ module TTFunk
13
15
  WE_HAVE_A_TWO_BY_TWO = 0x0080
14
16
  WE_HAVE_INSTRUCTIONS = 0x0100
15
17
 
16
- attr_reader :raw
18
+ attr_reader :id, :raw
19
+ attr_reader :number_of_contours
17
20
  attr_reader :x_min, :y_min, :x_max, :y_max
18
21
  attr_reader :glyph_ids
19
22
 
20
23
  Component = Struct.new(:flags, :glyph_index, :arg1, :arg2, :transform)
21
24
 
22
- def initialize(raw, x_min, y_min, x_max, y_max)
25
+ def initialize(id, raw)
26
+ @id = id
23
27
  @raw = raw
24
- @x_min = x_min
25
- @y_min = y_min
26
- @x_max = x_max
27
- @y_max = y_max
28
+ io = StringIO.new(raw)
29
+
30
+ @number_of_contours, @x_min, @y_min, @x_max, @y_max =
31
+ io.read(10).unpack('n*').map do |i|
32
+ BinUtils.twos_comp_to_int(i, bit_width: 16)
33
+ end
28
34
 
29
35
  # Because TTFunk only cares about glyphs insofar as they (1) provide
30
36
  # a bounding box for each glyph, and (2) can be rewritten into a
@@ -45,6 +51,7 @@ module TTFunk
45
51
  @glyph_id_offsets << offset + 2
46
52
 
47
53
  break unless flags & MORE_COMPONENTS != 0
54
+
48
55
  offset += 4
49
56
 
50
57
  offset +=
@@ -69,7 +76,7 @@ module TTFunk
69
76
  end
70
77
 
71
78
  def recode(mapping)
72
- result = @raw.dup
79
+ result = raw.dup
73
80
  new_ids = glyph_ids.map { |id| mapping[id] }
74
81
 
75
82
  new_ids.zip(@glyph_id_offsets).each do |new_id, offset|
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTFunk
4
+ class Table
5
+ class Glyf
6
+ class PathBased
7
+ attr_reader :path, :horizontal_metrics
8
+ attr_reader :x_min, :y_min, :x_max, :y_max
9
+ attr_reader :left_side_bearing, :right_side_bearing
10
+
11
+ def initialize(path, horizontal_metrics)
12
+ @path = path
13
+ @horizontal_metrics = horizontal_metrics
14
+
15
+ @x_min = 0
16
+ @y_min = 0
17
+ @x_max = horizontal_metrics.advance_width
18
+ @y_max = 0
19
+
20
+ path.commands.each do |command|
21
+ cmd, x, y = command
22
+ next if cmd == :close
23
+
24
+ @x_min = x if x < @x_min
25
+ @x_max = x if x > @x_max
26
+ @y_min = y if y < @y_min
27
+ @y_max = y if y > @y_max
28
+ end
29
+
30
+ @left_side_bearing = horizontal_metrics.left_side_bearing
31
+ @right_side_bearing =
32
+ horizontal_metrics.advance_width -
33
+ @left_side_bearing -
34
+ (@x_max - @x_min)
35
+ end
36
+
37
+ def number_of_contours
38
+ path.number_of_contours
39
+ end
40
+
41
+ def compound?
42
+ false
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -1,28 +1,30 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../../reader'
2
4
 
3
5
  module TTFunk
4
6
  class Table
5
7
  class Glyf
6
8
  class Simple
7
- attr_reader :raw
9
+ attr_reader :id, :raw
8
10
  attr_reader :number_of_contours
9
11
  attr_reader :x_min, :y_min, :x_max, :y_max
12
+ attr_reader :end_points_of_contours
13
+ attr_reader :instruction_length, :instructions
10
14
 
11
- def initialize(raw, number_of_contours, x_min, y_min, x_max, y_max)
15
+ def initialize(id, raw)
16
+ @id = id
12
17
  @raw = raw
13
- @number_of_contours = number_of_contours
14
- @x_min = x_min
15
- @y_min = y_min
16
- @x_max = x_max
17
- @y_max = y_max
18
-
19
- # Because TTFunk is, at this time, a library for simply pulling
20
- # metrics out of font files, or for writing font subsets, we don't
21
- # really care what the contours are for simple glyphs. We just
22
- # care that we've got an entire glyph's definition. Also, a
23
- # bounding box could be nice to know. Since we've got all that
24
- # at this point, we don't need to worry about parsing the full
25
- # contents of the glyph.
18
+ io = StringIO.new(raw)
19
+
20
+ @number_of_contours, @x_min, @y_min, @x_max, @y_max =
21
+ io.read(10).unpack('n*').map do |i|
22
+ BinUtils.twos_comp_to_int(i, bit_width: 16)
23
+ end
24
+
25
+ @end_points_of_contours = io.read(number_of_contours * 2).unpack('n*')
26
+ @instruction_length = io.read(2).unpack1('n')
27
+ @instructions = io.read(instruction_length).unpack('C*')
26
28
  end
27
29
 
28
30
  def compound?
@@ -32,6 +34,10 @@ module TTFunk
32
34
  def recode(_mapping)
33
35
  raw
34
36
  end
37
+
38
+ def end_point_of_last_contour
39
+ end_points_of_contours.last + 1
40
+ end
35
41
  end
36
42
  end
37
43
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../table'
2
4
 
3
5
  module TTFunk
@@ -21,11 +23,47 @@ module TTFunk
21
23
  attr_reader :index_to_loc_format
22
24
  attr_reader :glyph_data_format
23
25
 
24
- def self.encode(head, loca)
25
- table = head.raw
26
- table[8, 4] = "\0\0\0\0" # set checksum adjustment to 0 initially
27
- table[-4, 2] = [loca[:type]].pack('n') # set index_to_loc_format
28
- table
26
+ class << self
27
+ # mapping is new -> old glyph ids
28
+ def encode(head, loca, mapping)
29
+ EncodedString.new do |table|
30
+ table <<
31
+ [head.version, head.font_revision].pack('N2') <<
32
+ Placeholder.new(:checksum, length: 4) <<
33
+ [
34
+ head.magic_number,
35
+ head.flags, head.units_per_em,
36
+ head.created, head.modified,
37
+ *min_max_values_for(head, mapping),
38
+ head.mac_style, head.lowest_rec_ppem, head.font_direction_hint,
39
+ loca[:type] || 0, head.glyph_data_format
40
+ ].pack('Nn2q2n*')
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def min_max_values_for(head, mapping)
47
+ x_min = Min.new
48
+ x_max = Max.new
49
+ y_min = Min.new
50
+ y_max = Max.new
51
+
52
+ mapping.each do |_, old_glyph_id|
53
+ glyph = head.file.find_glyph(old_glyph_id)
54
+ next unless glyph
55
+
56
+ x_min << glyph.x_min
57
+ x_max << glyph.x_max
58
+ y_min << glyph.y_min
59
+ y_max << glyph.y_max
60
+ end
61
+
62
+ [
63
+ x_min.value_or(0), y_min.value_or(0),
64
+ x_max.value_or(0), y_max.value_or(0)
65
+ ]
66
+ end
29
67
  end
30
68
 
31
69
  private
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../table'
2
4
 
3
5
  module TTFunk
@@ -13,13 +15,54 @@ module TTFunk
13
15
  attr_reader :x_max_extent
14
16
  attr_reader :carot_slope_rise
15
17
  attr_reader :carot_slope_run
18
+ attr_reader :caret_offset
16
19
  attr_reader :metric_data_format
17
20
  attr_reader :number_of_metrics
18
21
 
19
- def self.encode(hhea, hmtx)
20
- raw = hhea.raw
21
- raw[-2, 2] = [hmtx[:number_of_metrics]].pack('n')
22
- raw
22
+ class << self
23
+ def encode(hhea, hmtx, original, mapping)
24
+ ''.b.tap do |table|
25
+ table << [hhea.version].pack('N')
26
+ table << [
27
+ hhea.ascent, hhea.descent, hhea.line_gap,
28
+ *min_max_values_for(original, mapping),
29
+ hhea.carot_slope_rise, hhea.carot_slope_run, hhea.caret_offset,
30
+ 0, 0, 0, 0, hhea.metric_data_format, hmtx[:number_of_metrics]
31
+ ].pack('n*')
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def min_max_values_for(original, mapping)
38
+ min_lsb = Min.new
39
+ min_rsb = Min.new
40
+ max_aw = Max.new
41
+ max_extent = Max.new
42
+
43
+ mapping.each do |_, old_glyph_id|
44
+ horiz_metrics = original.horizontal_metrics.for(old_glyph_id)
45
+ next unless horiz_metrics
46
+
47
+ min_lsb << horiz_metrics.left_side_bearing
48
+ max_aw << horiz_metrics.advance_width
49
+
50
+ glyph = original.find_glyph(old_glyph_id)
51
+ next unless glyph
52
+
53
+ x_delta = glyph.x_max - glyph.x_min
54
+
55
+ min_rsb << horiz_metrics.advance_width -
56
+ horiz_metrics.left_side_bearing - x_delta
57
+
58
+ max_extent << horiz_metrics.left_side_bearing + x_delta
59
+ end
60
+
61
+ [
62
+ max_aw.value_or(0), min_lsb.value_or(0),
63
+ min_rsb.value_or(0), max_extent.value_or(0)
64
+ ]
65
+ end
23
66
  end
24
67
 
25
68
  private
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../table'
2
4
 
3
5
  module TTFunk
@@ -23,14 +25,19 @@ module TTFunk
23
25
 
24
26
  def for(glyph_id)
25
27
  @metrics[glyph_id] ||
26
- HorizontalMetric.new(
27
- @metrics.last.advance_width,
28
- @left_side_bearings[glyph_id - @metrics.length]
29
- )
28
+ metrics_cache[glyph_id] ||=
29
+ HorizontalMetric.new(
30
+ @metrics.last.advance_width,
31
+ @left_side_bearings[glyph_id - @metrics.length]
32
+ )
30
33
  end
31
34
 
32
35
  private
33
36
 
37
+ def metrics_cache
38
+ @metrics_cache ||= {}
39
+ end
40
+
34
41
  def parse!
35
42
  @metrics = []
36
43
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../table'
2
4
 
3
5
  module TTFunk
@@ -8,6 +10,7 @@ module TTFunk
8
10
 
9
11
  def self.encode(kerning, mapping)
10
12
  return nil unless kerning.exists? && kerning.tables.any?
13
+
11
14
  tables = kerning.tables.map { |table| table.recode(mapping) }.compact
12
15
  return nil if tables.empty?
13
16
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../../reader'
2
4
 
3
5
  module TTFunk
@@ -18,6 +20,7 @@ module TTFunk
18
20
  num_pairs.times do |i|
19
21
  # sanity check, in case there's a bad length somewhere
20
22
  break if i * 3 + 2 > pairs.length
23
+
21
24
  left = pairs[i * 3]
22
25
  right = pairs[i * 3 + 1]
23
26
  value = to_signed(pairs[i * 3 + 2])
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../table'
2
4
 
3
5
  module TTFunk
@@ -1,8 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../table'
2
4
 
3
5
  module TTFunk
4
6
  class Table
5
7
  class Maxp < Table
8
+ DEFAULT_MAX_COMPONENT_DEPTH = 1
9
+ MAX_V1_TABLE_LENGTH = 34
10
+
6
11
  attr_reader :version
7
12
  attr_reader :num_glyphs
8
13
  attr_reader :max_points
@@ -19,21 +24,150 @@ module TTFunk
19
24
  attr_reader :max_component_elements
20
25
  attr_reader :max_component_depth
21
26
 
22
- def self.encode(maxp, mapping)
23
- num_glyphs = mapping.length
24
- raw = maxp.raw
25
- raw[4, 2] = [num_glyphs].pack('n')
26
- raw
27
+ class << self
28
+ def encode(maxp, new2old_glyph)
29
+ ''.b.tap do |table|
30
+ num_glyphs = new2old_glyph.length
31
+ table << [maxp.version, num_glyphs].pack('Nn')
32
+
33
+ if maxp.version == 0x10000
34
+ stats = stats_for(
35
+ maxp, glyphs_from_ids(maxp, new2old_glyph.values)
36
+ )
37
+
38
+ table << [
39
+ stats[:max_points],
40
+ stats[:max_contours],
41
+ stats[:max_component_points],
42
+ stats[:max_component_contours],
43
+ # these all come from the fpgm and cvt tables, which
44
+ # we don't support at the moment
45
+ maxp.max_zones,
46
+ maxp.max_twilight_points,
47
+ maxp.max_storage,
48
+ maxp.max_function_defs,
49
+ maxp.max_instruction_defs,
50
+ maxp.max_stack_elements,
51
+ stats[:max_size_of_instructions],
52
+ stats[:max_component_elements],
53
+ stats[:max_component_depth]
54
+ ].pack('n*')
55
+ end
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def glyphs_from_ids(maxp, glyph_ids)
62
+ glyph_ids.each_with_object([]) do |glyph_id, ret|
63
+ if (glyph = maxp.file.glyph_outlines.for(glyph_id))
64
+ ret << glyph
65
+ end
66
+ end
67
+ end
68
+
69
+ def stats_for(maxp, glyphs)
70
+ stats_for_simple(maxp, glyphs)
71
+ .merge(stats_for_compound(maxp, glyphs))
72
+ .each_with_object({}) do |(name, agg), ret|
73
+ ret[name] = agg.value_or(0)
74
+ end
75
+ end
76
+
77
+ def stats_for_simple(_maxp, glyphs)
78
+ max_component_elements = Max.new
79
+ max_points = Max.new
80
+ max_contours = Max.new
81
+ max_size_of_instructions = Max.new
82
+
83
+ glyphs.each do |glyph|
84
+ if glyph.compound?
85
+ max_component_elements << glyph.glyph_ids.size
86
+ else
87
+ max_points << glyph.end_point_of_last_contour
88
+ max_contours << glyph.number_of_contours
89
+ max_size_of_instructions << glyph.instruction_length
90
+ end
91
+ end
92
+
93
+ {
94
+ max_component_elements: max_component_elements,
95
+ max_points: max_points,
96
+ max_contours: max_contours,
97
+ max_size_of_instructions: max_size_of_instructions
98
+ }
99
+ end
100
+
101
+ def stats_for_compound(maxp, glyphs)
102
+ max_component_points = Max.new
103
+ max_component_depth = Max.new
104
+ max_component_contours = Max.new
105
+
106
+ glyphs.each do |glyph|
107
+ next unless glyph.compound?
108
+
109
+ stats = totals_for_compound(maxp, [glyph], 0)
110
+ max_component_points << stats[:total_points]
111
+ max_component_depth << stats[:max_depth]
112
+ max_component_contours << stats[:total_contours]
113
+ end
114
+
115
+ {
116
+ max_component_points: max_component_points,
117
+ max_component_depth: max_component_depth,
118
+ max_component_contours: max_component_contours
119
+ }
120
+ end
121
+
122
+ def totals_for_compound(maxp, glyphs, depth)
123
+ total_points = Sum.new
124
+ total_contours = Sum.new
125
+ max_depth = Max.new(depth)
126
+
127
+ glyphs.each do |glyph|
128
+ if glyph.compound?
129
+ stats = totals_for_compound(
130
+ maxp, glyphs_from_ids(maxp, glyph.glyph_ids), depth + 1
131
+ )
132
+
133
+ total_points << stats[:total_points]
134
+ total_contours << stats[:total_contours]
135
+ max_depth << stats[:max_depth]
136
+ else
137
+ stats = stats_for_simple(maxp, [glyph])
138
+ total_points << stats[:max_points]
139
+ total_contours << stats[:max_contours]
140
+ end
141
+ end
142
+
143
+ {
144
+ total_points: total_points,
145
+ total_contours: total_contours,
146
+ max_depth: max_depth
147
+ }
148
+ end
27
149
  end
28
150
 
29
151
  private
30
152
 
31
153
  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*')
154
+ @version, @num_glyphs = read(6, 'Nn')
155
+
156
+ if @version == 0x10000
157
+ @max_points, @max_contours, @max_component_points,
158
+ @max_component_contours, @max_zones, @max_twilight_points,
159
+ @max_storage, @max_function_defs, @max_instruction_defs,
160
+ @max_stack_elements, @max_size_of_instructions,
161
+ @max_component_elements = read(26, 'Nn*')
162
+
163
+ # a number of fonts omit these last two bytes for some reason,
164
+ # so we have to supply a default here to prevent nils
165
+ @max_component_depth = if length == MAX_V1_TABLE_LENGTH
166
+ read(2, 'n').first
167
+ else
168
+ DEFAULT_MAX_COMPONENT_DEPTH
169
+ end
170
+ end
37
171
  end
38
172
  end
39
173
  end