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.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/CHANGELOG.md +60 -0
- data/README.md +2 -1
- data/lib/ttfunk.rb +45 -0
- data/lib/ttfunk/aggregate.rb +15 -0
- data/lib/ttfunk/bin_utils.rb +47 -0
- data/lib/ttfunk/bit_field.rb +31 -0
- data/lib/ttfunk/collection.rb +3 -1
- data/lib/ttfunk/directory.rb +6 -0
- data/lib/ttfunk/encoded_string.rb +97 -0
- data/lib/ttfunk/max.rb +25 -0
- data/lib/ttfunk/min.rb +25 -0
- data/lib/ttfunk/one_based_array.rb +36 -0
- data/lib/ttfunk/otf_encoder.rb +61 -0
- data/lib/ttfunk/placeholder.rb +13 -0
- data/lib/ttfunk/reader.rb +34 -32
- data/lib/ttfunk/resource_file.rb +7 -5
- data/lib/ttfunk/sci_form.rb +29 -0
- data/lib/ttfunk/sub_table.rb +38 -0
- data/lib/ttfunk/subset.rb +2 -0
- data/lib/ttfunk/subset/base.rb +61 -120
- data/lib/ttfunk/subset/code_page.rb +89 -0
- data/lib/ttfunk/subset/mac_roman.rb +5 -42
- data/lib/ttfunk/subset/unicode.rb +12 -6
- data/lib/ttfunk/subset/unicode_8bit.rb +14 -12
- data/lib/ttfunk/subset/windows_1252.rb +5 -47
- data/lib/ttfunk/subset_collection.rb +4 -0
- data/lib/ttfunk/sum.rb +20 -0
- data/lib/ttfunk/table.rb +4 -0
- data/lib/ttfunk/table/cff.rb +69 -0
- data/lib/ttfunk/table/cff/charset.rb +212 -0
- data/lib/ttfunk/table/cff/charsets.rb +14 -0
- data/lib/ttfunk/table/cff/charsets/expert.rb +189 -0
- data/lib/ttfunk/table/cff/charsets/expert_subset.rb +119 -0
- data/lib/ttfunk/table/cff/charsets/iso_adobe.rb +241 -0
- data/lib/ttfunk/table/cff/charsets/standard_strings.rb +404 -0
- data/lib/ttfunk/table/cff/charstring.rb +487 -0
- data/lib/ttfunk/table/cff/charstrings_index.rb +39 -0
- data/lib/ttfunk/table/cff/dict.rb +266 -0
- data/lib/ttfunk/table/cff/encoding.rb +220 -0
- data/lib/ttfunk/table/cff/encodings.rb +12 -0
- data/lib/ttfunk/table/cff/encodings/expert.rb +206 -0
- data/lib/ttfunk/table/cff/encodings/standard.rb +181 -0
- data/lib/ttfunk/table/cff/fd_selector.rb +150 -0
- data/lib/ttfunk/table/cff/font_dict.rb +79 -0
- data/lib/ttfunk/table/cff/font_index.rb +29 -0
- data/lib/ttfunk/table/cff/header.rb +33 -0
- data/lib/ttfunk/table/cff/index.rb +125 -0
- data/lib/ttfunk/table/cff/one_based_index.rb +31 -0
- data/lib/ttfunk/table/cff/path.rb +66 -0
- data/lib/ttfunk/table/cff/private_dict.rb +84 -0
- data/lib/ttfunk/table/cff/subr_index.rb +19 -0
- data/lib/ttfunk/table/cff/top_dict.rb +230 -0
- data/lib/ttfunk/table/cff/top_index.rb +16 -0
- data/lib/ttfunk/table/cmap.rb +4 -4
- data/lib/ttfunk/table/cmap/format00.rb +1 -2
- data/lib/ttfunk/table/cmap/format04.rb +11 -3
- data/lib/ttfunk/table/cmap/format06.rb +2 -0
- data/lib/ttfunk/table/cmap/format10.rb +2 -0
- data/lib/ttfunk/table/cmap/format12.rb +2 -0
- data/lib/ttfunk/table/cmap/subtable.rb +12 -8
- data/lib/ttfunk/table/dsig.rb +50 -0
- data/lib/ttfunk/table/glyf.rb +11 -9
- data/lib/ttfunk/table/glyf/compound.rb +14 -7
- data/lib/ttfunk/table/glyf/path_based.rb +47 -0
- data/lib/ttfunk/table/glyf/simple.rb +21 -15
- data/lib/ttfunk/table/head.rb +43 -5
- data/lib/ttfunk/table/hhea.rb +47 -4
- data/lib/ttfunk/table/hmtx.rb +11 -4
- data/lib/ttfunk/table/kern.rb +3 -0
- data/lib/ttfunk/table/kern/format0.rb +3 -0
- data/lib/ttfunk/table/loca.rb +2 -0
- data/lib/ttfunk/table/maxp.rb +144 -10
- data/lib/ttfunk/table/name.rb +75 -37
- data/lib/ttfunk/table/os2.rb +327 -4
- data/lib/ttfunk/table/post.rb +8 -1
- data/lib/ttfunk/table/post/format10.rb +2 -0
- data/lib/ttfunk/table/post/format20.rb +5 -1
- data/lib/ttfunk/table/post/format30.rb +2 -0
- data/lib/ttfunk/table/post/format40.rb +2 -0
- data/lib/ttfunk/table/sbix.rb +2 -0
- data/lib/ttfunk/table/simple.rb +2 -0
- data/lib/ttfunk/table/vorg.rb +54 -0
- data/lib/ttfunk/ttf_encoder.rb +220 -0
- metadata +88 -20
- metadata.gz.sig +0 -0
- data/lib/ttfunk/encoding/mac_roman.rb +0 -100
- 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
|