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