ttfunk 1.4.0 → 1.5.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/CHANGELOG.md +114 -0
- data/{README.rdoc → README.md} +12 -8
- data/lib/ttfunk.rb +27 -21
- data/lib/ttfunk/collection.rb +41 -0
- data/lib/ttfunk/directory.rb +10 -4
- data/lib/ttfunk/encoding/mac_roman.rb +50 -40
- data/lib/ttfunk/encoding/windows_1252.rb +28 -22
- data/lib/ttfunk/reader.rb +11 -10
- data/lib/ttfunk/resource_file.rb +28 -21
- data/lib/ttfunk/subset.rb +5 -5
- data/lib/ttfunk/subset/base.rb +66 -38
- data/lib/ttfunk/subset/mac_roman.rb +11 -10
- data/lib/ttfunk/subset/unicode.rb +10 -8
- data/lib/ttfunk/subset/unicode_8bit.rb +16 -16
- data/lib/ttfunk/subset/windows_1252.rb +16 -15
- data/lib/ttfunk/subset_collection.rb +7 -8
- data/lib/ttfunk/table.rb +3 -5
- data/lib/ttfunk/table/cmap.rb +13 -13
- data/lib/ttfunk/table/cmap/format00.rb +8 -10
- data/lib/ttfunk/table/cmap/format04.rb +37 -32
- data/lib/ttfunk/table/cmap/format06.rb +16 -16
- data/lib/ttfunk/table/cmap/format10.rb +22 -17
- data/lib/ttfunk/table/cmap/format12.rb +28 -22
- data/lib/ttfunk/table/cmap/subtable.rb +30 -23
- data/lib/ttfunk/table/glyf.rb +13 -11
- data/lib/ttfunk/table/glyf/compound.rb +13 -10
- data/lib/ttfunk/table/glyf/simple.rb +5 -4
- data/lib/ttfunk/table/head.rb +13 -13
- data/lib/ttfunk/table/hhea.rb +13 -13
- data/lib/ttfunk/table/hmtx.rb +23 -18
- data/lib/ttfunk/table/kern.rb +57 -48
- data/lib/ttfunk/table/kern/format0.rb +18 -10
- data/lib/ttfunk/table/loca.rb +9 -9
- data/lib/ttfunk/table/maxp.rb +9 -9
- data/lib/ttfunk/table/name.rb +65 -56
- data/lib/ttfunk/table/os2.rb +21 -21
- data/lib/ttfunk/table/post.rb +32 -32
- data/lib/ttfunk/table/post/format10.rb +33 -27
- data/lib/ttfunk/table/post/format20.rb +10 -10
- data/lib/ttfunk/table/post/format30.rb +5 -5
- data/lib/ttfunk/table/post/format40.rb +3 -3
- data/lib/ttfunk/table/sbix.rb +19 -10
- metadata +55 -32
- metadata.gz.sig +0 -0
- data/CHANGELOG +0 -5
- data/data/fonts/DejaVuSans.ttf +0 -0
- data/examples/metrics.rb +0 -45
data/lib/ttfunk/table/kern.rb
CHANGED
@@ -11,67 +11,76 @@ module TTFunk
|
|
11
11
|
tables = kerning.tables.map { |table| table.recode(mapping) }.compact
|
12
12
|
return nil if tables.empty?
|
13
13
|
|
14
|
-
[0, tables.length, tables.join].pack(
|
14
|
+
[0, tables.length, tables.join].pack('nnA*')
|
15
15
|
end
|
16
16
|
|
17
17
|
private
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
def parse!
|
20
|
+
@version, num_tables = read(4, 'n*')
|
21
|
+
@tables = []
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
end
|
23
|
+
if @version == 1 # Mac OS X fonts
|
24
|
+
@version = (@version << 16) + num_tables
|
25
|
+
num_tables = read(4, 'N').first
|
26
|
+
parse_version_1_tables(num_tables)
|
27
|
+
else
|
28
|
+
parse_version_0_tables(num_tables)
|
30
29
|
end
|
30
|
+
end
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
32
|
+
def parse_version_0_tables(_num_tables)
|
33
|
+
# It looks like some MS fonts report their kerning subtable lengths
|
34
|
+
# wrong. In one case, the length was reported to be some 19366, and yet
|
35
|
+
# the table also claimed to hold 14148 pairs (each pair consisting of
|
36
|
+
# 6 bytes). You do the math!
|
37
|
+
#
|
38
|
+
# We're going to assume that the microsoft fonts hold only a single
|
39
|
+
# kerning subtable, which occupies the entire length of the kerning
|
40
|
+
# table. Worst case, we lose any other subtables that the font contains,
|
41
|
+
# but it's better than reading a truncated kerning table.
|
42
|
+
#
|
43
|
+
# And what's more, it appears to work. So.
|
44
|
+
version, length, coverage = read(6, 'n*')
|
45
|
+
format = coverage >> 8
|
46
46
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
47
|
+
add_table(
|
48
|
+
format,
|
49
|
+
version: version,
|
50
|
+
length: length,
|
51
|
+
coverage: coverage,
|
52
|
+
data: raw[10..-1],
|
53
|
+
vertical: (coverage & 0x1 == 0),
|
54
|
+
minimum: (coverage & 0x2 != 0),
|
55
|
+
cross: (coverage & 0x4 != 0),
|
56
|
+
override: (coverage & 0x8 != 0)
|
57
|
+
)
|
58
|
+
end
|
54
59
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
60
|
+
def parse_version_1_tables(num_tables)
|
61
|
+
num_tables.times do
|
62
|
+
length, coverage, tuple_index = read(8, 'Nnn')
|
63
|
+
format = coverage & 0x0FF
|
59
64
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
65
|
+
add_table(
|
66
|
+
format,
|
67
|
+
length: length,
|
68
|
+
coverage: coverage,
|
69
|
+
tuple_index: tuple_index,
|
70
|
+
data: io.read(length - 8),
|
71
|
+
vertical: (coverage & 0x8000 != 0),
|
72
|
+
cross: (coverage & 0x4000 != 0),
|
73
|
+
variation: (coverage & 0x2000 != 0)
|
74
|
+
)
|
66
75
|
end
|
76
|
+
end
|
67
77
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
else
|
72
|
-
# silently ignore unsupported kerning tables
|
73
|
-
end
|
78
|
+
def add_table(format, attributes = {})
|
79
|
+
if format == 0
|
80
|
+
@tables << Kern::Format0.new(attributes)
|
74
81
|
end
|
82
|
+
# Unsupported kerning tables are silently ignored
|
83
|
+
end
|
75
84
|
end
|
76
85
|
end
|
77
86
|
end
|
@@ -9,17 +9,18 @@ module TTFunk
|
|
9
9
|
attr_reader :attributes
|
10
10
|
attr_reader :pairs
|
11
11
|
|
12
|
-
def initialize(attributes={})
|
12
|
+
def initialize(attributes = {})
|
13
13
|
@attributes = attributes
|
14
14
|
|
15
|
-
num_pairs, *pairs = attributes.delete(:data).unpack(
|
15
|
+
num_pairs, *pairs = attributes.delete(:data).unpack('nx6n*')
|
16
16
|
|
17
17
|
@pairs = {}
|
18
18
|
num_pairs.times do |i|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
# sanity check, in case there's a bad length somewhere
|
20
|
+
break if i * 3 + 2 > pairs.length
|
21
|
+
left = pairs[i * 3]
|
22
|
+
right = pairs[i * 3 + 1]
|
23
|
+
value = to_signed(pairs[i * 3 + 2])
|
23
24
|
@pairs[[left, right]] = value
|
24
25
|
end
|
25
26
|
end
|
@@ -47,13 +48,20 @@ module TTFunk
|
|
47
48
|
return nil if subset.empty?
|
48
49
|
|
49
50
|
num_pairs = subset.length
|
50
|
-
search_range = 2 * 2
|
51
|
+
search_range = 2 * 2**(Math.log(num_pairs) / Math.log(2)).to_i
|
51
52
|
entry_selector = (Math.log(search_range / 2) / Math.log(2)).to_i
|
52
53
|
range_shift = (2 * num_pairs) - search_range
|
53
54
|
|
54
|
-
[
|
55
|
-
|
56
|
-
|
55
|
+
[
|
56
|
+
attributes[:version],
|
57
|
+
num_pairs * 6 + 14,
|
58
|
+
attributes[:coverage],
|
59
|
+
num_pairs,
|
60
|
+
search_range,
|
61
|
+
entry_selector,
|
62
|
+
range_shift,
|
63
|
+
subset
|
64
|
+
].flatten.pack('n*')
|
57
65
|
end
|
58
66
|
end
|
59
67
|
end
|
data/lib/ttfunk/table/loca.rb
CHANGED
@@ -14,9 +14,9 @@ module TTFunk
|
|
14
14
|
# * :type - the type of offset (to be encoded in the 'head' table)
|
15
15
|
def self.encode(offsets)
|
16
16
|
if offsets.any? { |ofs| ofs > 0xFFFF }
|
17
|
-
{ :
|
17
|
+
{ type: 1, table: offsets.pack('N*') }
|
18
18
|
else
|
19
|
-
{ :
|
19
|
+
{ type: 0, table: offsets.map { |o| o / 2 }.pack('n*') }
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
@@ -25,19 +25,19 @@ module TTFunk
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def size_of(glyph_id)
|
28
|
-
@offsets[glyph_id+1] - @offsets[glyph_id]
|
28
|
+
@offsets[glyph_id + 1] - @offsets[glyph_id]
|
29
29
|
end
|
30
30
|
|
31
31
|
private
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
def parse!
|
34
|
+
type = file.header.index_to_loc_format == 0 ? 'n' : 'N'
|
35
|
+
@offsets = read(length, "#{type}*")
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
end
|
37
|
+
if file.header.index_to_loc_format == 0
|
38
|
+
@offsets.map! { |v| v * 2 }
|
40
39
|
end
|
40
|
+
end
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
data/lib/ttfunk/table/maxp.rb
CHANGED
@@ -22,19 +22,19 @@ module TTFunk
|
|
22
22
|
def self.encode(maxp, mapping)
|
23
23
|
num_glyphs = mapping.length
|
24
24
|
raw = maxp.raw
|
25
|
-
raw[4,2] = [num_glyphs].pack(
|
26
|
-
|
25
|
+
raw[4, 2] = [num_glyphs].pack('n')
|
26
|
+
raw
|
27
27
|
end
|
28
28
|
|
29
29
|
private
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
31
|
+
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*')
|
37
|
+
end
|
38
38
|
end
|
39
39
|
end
|
40
40
|
end
|
data/lib/ttfunk/table/name.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require_relative '../table'
|
2
|
+
require 'digest/sha1'
|
2
3
|
|
3
4
|
module TTFunk
|
4
5
|
class Table
|
@@ -16,9 +17,9 @@ module TTFunk
|
|
16
17
|
end
|
17
18
|
|
18
19
|
def strip_extended
|
19
|
-
stripped = gsub(/[\x00-\x19\x80-\xff]/n,
|
20
|
-
stripped =
|
21
|
-
|
20
|
+
stripped = gsub(/[\x00-\x19\x80-\xff]/n, '')
|
21
|
+
stripped = '[not-postscript]' if stripped.empty?
|
22
|
+
stripped
|
22
23
|
end
|
23
24
|
end
|
24
25
|
|
@@ -43,24 +44,26 @@ module TTFunk
|
|
43
44
|
attr_reader :compatible_full
|
44
45
|
attr_reader :sample_text
|
45
46
|
|
46
|
-
|
47
|
+
def self.encode(names, key = '')
|
48
|
+
tag = Digest::SHA1.hexdigest(key)[0, 6]
|
47
49
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
postscript_name = Name::String.new("#{tag}+#{names.postscript_name}", 1, 0, 0)
|
50
|
+
postscript_name = Name::String.new(
|
51
|
+
"#{tag}+#{names.postscript_name}", 1, 0, 0
|
52
|
+
)
|
53
53
|
|
54
54
|
strings = names.strings.dup
|
55
55
|
strings[6] = [postscript_name]
|
56
56
|
str_count = strings.inject(0) { |sum, (_, list)| sum + list.length }
|
57
57
|
|
58
|
-
table = [0, str_count, 6 + 12 * str_count].pack(
|
59
|
-
strtable =
|
58
|
+
table = [0, str_count, 6 + 12 * str_count].pack('n*')
|
59
|
+
strtable = ''
|
60
60
|
|
61
61
|
strings.each do |id, list|
|
62
62
|
list.each do |string|
|
63
|
-
table << [
|
63
|
+
table << [
|
64
|
+
string.platform_id, string.encoding_id, string.language_id, id,
|
65
|
+
string.length, strtable.length
|
66
|
+
].pack('n*')
|
64
67
|
strtable << string
|
65
68
|
end
|
66
69
|
end
|
@@ -70,56 +73,62 @@ module TTFunk
|
|
70
73
|
|
71
74
|
def postscript_name
|
72
75
|
return @postscript_name if @postscript_name
|
73
|
-
font_family.first ||
|
76
|
+
font_family.first || 'unnamed'
|
74
77
|
end
|
75
78
|
|
76
79
|
private
|
77
80
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
@strings = Hash.new { |h,k| h[k] = [] }
|
95
|
-
|
96
|
-
count.times do |i|
|
97
|
-
io.pos = entries[i][:offset]
|
98
|
-
text = io.read(entries[i][:length])
|
99
|
-
@strings[entries[i][:name_id]] << Name::String.new(text,
|
100
|
-
entries[i][:platform_id], entries[i][:encoding_id], entries[i][:language_id])
|
101
|
-
end
|
81
|
+
def parse!
|
82
|
+
count, string_offset = read(6, 'x2n*')
|
83
|
+
|
84
|
+
entries = []
|
85
|
+
count.times do
|
86
|
+
platform, encoding, language, id, length, start_offset =
|
87
|
+
read(12, 'n*')
|
88
|
+
entries << {
|
89
|
+
platform_id: platform,
|
90
|
+
encoding_id: encoding,
|
91
|
+
language_id: language,
|
92
|
+
name_id: id,
|
93
|
+
length: length,
|
94
|
+
offset: offset + string_offset + start_offset
|
95
|
+
}
|
96
|
+
end
|
102
97
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
@
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
@vendor_url = @strings[11]
|
115
|
-
@designer_url = @strings[12]
|
116
|
-
@license = @strings[13]
|
117
|
-
@license_url = @strings[14]
|
118
|
-
@preferred_family = @strings[16]
|
119
|
-
@preferred_subfamily = @strings[17]
|
120
|
-
@compatible_full = @strings[18]
|
121
|
-
@sample_text = @strings[19]
|
98
|
+
@strings = Hash.new { |h, k| h[k] = [] }
|
99
|
+
|
100
|
+
count.times do |i|
|
101
|
+
io.pos = entries[i][:offset]
|
102
|
+
text = io.read(entries[i][:length])
|
103
|
+
@strings[entries[i][:name_id]] << Name::String.new(
|
104
|
+
text,
|
105
|
+
entries[i][:platform_id],
|
106
|
+
entries[i][:encoding_id],
|
107
|
+
entries[i][:language_id]
|
108
|
+
)
|
122
109
|
end
|
110
|
+
|
111
|
+
@copyright = @strings[0]
|
112
|
+
@font_family = @strings[1]
|
113
|
+
@font_subfamily = @strings[2]
|
114
|
+
@unique_subfamily = @strings[3]
|
115
|
+
@font_name = @strings[4]
|
116
|
+
@version = @strings[5]
|
117
|
+
# should only be ONE postscript name
|
118
|
+
@postscript_name = @strings[6].first.strip_extended
|
119
|
+
@trademark = @strings[7]
|
120
|
+
@manufacturer = @strings[8]
|
121
|
+
@designer = @strings[9]
|
122
|
+
@description = @strings[10]
|
123
|
+
@vendor_url = @strings[11]
|
124
|
+
@designer_url = @strings[12]
|
125
|
+
@license = @strings[13]
|
126
|
+
@license_url = @strings[14]
|
127
|
+
@preferred_family = @strings[16]
|
128
|
+
@preferred_subfamily = @strings[17]
|
129
|
+
@compatible_full = @strings[18]
|
130
|
+
@sample_text = @strings[19]
|
131
|
+
end
|
123
132
|
end
|
124
133
|
end
|
125
134
|
end
|
data/lib/ttfunk/table/os2.rb
CHANGED
@@ -41,38 +41,38 @@ module TTFunk
|
|
41
41
|
attr_reader :max_context
|
42
42
|
|
43
43
|
def tag
|
44
|
-
|
44
|
+
'OS/2'
|
45
45
|
end
|
46
46
|
|
47
47
|
private
|
48
48
|
|
49
|
-
|
50
|
-
|
49
|
+
def parse!
|
50
|
+
@version = read(2, 'n').first
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
52
|
+
@ave_char_width = read_signed(1)
|
53
|
+
@weight_class, @width_class = read(4, 'nn')
|
54
|
+
@type, @y_subscript_x_size, @y_subscript_y_size, @y_subscript_x_offset,
|
55
|
+
@y_subscript_y_offset, @y_superscript_x_size, @y_superscript_y_size,
|
56
|
+
@y_superscript_x_offset, @y_superscript_y_offset, @y_strikeout_size,
|
57
|
+
@y_strikeout_position, @family_class = read_signed(12)
|
58
|
+
@panose = io.read(10)
|
59
59
|
|
60
|
-
|
61
|
-
|
60
|
+
@char_range = io.read(16)
|
61
|
+
@vendor_id = io.read(4)
|
62
62
|
|
63
|
-
|
63
|
+
@selection, @first_char_index, @last_char_index = read(6, 'n*')
|
64
64
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
65
|
+
if @version > 0
|
66
|
+
@ascent, @descent, @line_gap = read_signed(3)
|
67
|
+
@win_ascent, @win_descent = read(4, 'nn')
|
68
|
+
@code_page_range = io.read(8)
|
69
69
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
end
|
70
|
+
if @version > 1
|
71
|
+
@x_height, @cap_height = read_signed(2)
|
72
|
+
@default_char, @break_char, @max_context = read(6, 'nnn')
|
74
73
|
end
|
75
74
|
end
|
75
|
+
end
|
76
76
|
end
|
77
77
|
end
|
78
78
|
end
|