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
@@ -20,7 +20,7 @@ module TTFunk
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def use(character)
|
23
|
-
|
23
|
+
unless @unicodes.key?(character)
|
24
24
|
@subset[@next] = character
|
25
25
|
@unicodes[character] = @next
|
26
26
|
@next += 1
|
@@ -41,23 +41,23 @@ module TTFunk
|
|
41
41
|
|
42
42
|
protected
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
end
|
49
|
-
|
50
|
-
# since we're mapping a subset of the unicode glyphs into an
|
51
|
-
# arbitrary 256-character space, the actual encoding we're
|
52
|
-
# using is irrelevant. We choose MacRoman because it's a 256-character
|
53
|
-
# encoding that happens to be well-supported in both TTF and
|
54
|
-
# PDF formats.
|
55
|
-
TTFunk::Table::Cmap.encode(mapping, :mac_roman)
|
44
|
+
def new_cmap_table(_options)
|
45
|
+
mapping = @subset.each_with_object({}) do |(code, unicode), map|
|
46
|
+
map[code] = unicode_cmap[unicode]
|
47
|
+
map
|
56
48
|
end
|
57
49
|
|
58
|
-
|
59
|
-
|
60
|
-
|
50
|
+
# since we're mapping a subset of the unicode glyphs into an
|
51
|
+
# arbitrary 256-character space, the actual encoding we're
|
52
|
+
# using is irrelevant. We choose MacRoman because it's a 256-character
|
53
|
+
# encoding that happens to be well-supported in both TTF and
|
54
|
+
# PDF formats.
|
55
|
+
TTFunk::Table::Cmap.encode(mapping, :mac_roman)
|
56
|
+
end
|
57
|
+
|
58
|
+
def original_glyph_ids
|
59
|
+
([0] + @unicodes.keys.map { |unicode| unicode_cmap[unicode] }).uniq.sort
|
60
|
+
end
|
61
61
|
end
|
62
62
|
end
|
63
63
|
end
|
@@ -34,23 +34,24 @@ module TTFunk
|
|
34
34
|
|
35
35
|
protected
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
end
|
42
|
-
|
43
|
-
# yes, I really mean "mac roman". TTF has no cp1252 encoding, and the
|
44
|
-
# alternative would be to encode it using a format 4 unicode table, which
|
45
|
-
# is overkill. for our purposes, mac-roman suffices. (If we were building
|
46
|
-
# a _real_ font, instead of a PDF-embeddable subset, things would probably
|
47
|
-
# be different.)
|
48
|
-
TTFunk::Table::Cmap.encode(mapping, :mac_roman)
|
37
|
+
def new_cmap_table(_options)
|
38
|
+
mapping = {}
|
39
|
+
@subset.each_with_index do |unicode, cp1252|
|
40
|
+
mapping[cp1252] = unicode_cmap[unicode] if cp1252
|
49
41
|
end
|
50
42
|
|
51
|
-
|
52
|
-
|
53
|
-
|
43
|
+
# yes, I really mean "mac roman". TTF has no cp1252 encoding, and the
|
44
|
+
# alternative would be to encode it using a format 4 unicode table,
|
45
|
+
# which is overkill. for our purposes, mac-roman suffices. (If we were
|
46
|
+
# building a _real_ font, instead of a PDF-embeddable subset, things
|
47
|
+
# would probably be different.)
|
48
|
+
TTFunk::Table::Cmap.encode(mapping, :mac_roman)
|
49
|
+
end
|
50
|
+
|
51
|
+
def original_glyph_ids
|
52
|
+
([0] + @subset.map { |unicode| unicode && unicode_cmap[unicode] })
|
53
|
+
.compact.uniq.sort
|
54
|
+
end
|
54
55
|
end
|
55
56
|
end
|
56
57
|
end
|
@@ -15,15 +15,14 @@ module TTFunk
|
|
15
15
|
def use(characters)
|
16
16
|
characters.each do |char|
|
17
17
|
covered = false
|
18
|
-
@subsets.each_with_index do |subset,
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
18
|
+
@subsets.each_with_index do |subset, _i|
|
19
|
+
next unless subset.covers?(char)
|
20
|
+
subset.use(char)
|
21
|
+
covered = true
|
22
|
+
break
|
24
23
|
end
|
25
24
|
|
26
|
-
|
25
|
+
unless covered
|
27
26
|
@subsets << Subset.for(@original, :unicode_8bit)
|
28
27
|
@subsets.last.use(char)
|
29
28
|
end
|
@@ -57,7 +56,7 @@ module TTFunk
|
|
57
56
|
if parts.empty? || parts.last[0] != current_subset
|
58
57
|
encoded_char = char.chr
|
59
58
|
if encoded_char.respond_to?(:force_encoding)
|
60
|
-
encoded_char.force_encoding(
|
59
|
+
encoded_char.force_encoding('ASCII-8BIT')
|
61
60
|
end
|
62
61
|
parts << [current_subset, encoded_char]
|
63
62
|
else
|
data/lib/ttfunk/table.rb
CHANGED
@@ -28,8 +28,6 @@ module TTFunk
|
|
28
28
|
def raw
|
29
29
|
if exists?
|
30
30
|
parse_from(offset) { io.read(length) }
|
31
|
-
else
|
32
|
-
nil
|
33
31
|
end
|
34
32
|
end
|
35
33
|
|
@@ -39,8 +37,8 @@ module TTFunk
|
|
39
37
|
|
40
38
|
private
|
41
39
|
|
42
|
-
|
43
|
-
|
44
|
-
|
40
|
+
def parse!
|
41
|
+
# do nothing, by default
|
42
|
+
end
|
45
43
|
end
|
46
44
|
end
|
data/lib/ttfunk/table/cmap.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
module TTFunk
|
1
|
+
module TTFunk
|
2
2
|
class Table
|
3
3
|
class Cmap < Table
|
4
4
|
attr_reader :version
|
@@ -8,29 +8,29 @@ module TTFunk
|
|
8
8
|
result = Cmap::Subtable.encode(charmap, encoding)
|
9
9
|
|
10
10
|
# pack 'version' and 'table-count'
|
11
|
-
result[:table] = [0, 1, result.delete(:subtable)].pack(
|
12
|
-
|
11
|
+
result[:table] = [0, 1, result.delete(:subtable)].pack('nnA*')
|
12
|
+
result
|
13
13
|
end
|
14
14
|
|
15
15
|
def unicode
|
16
|
-
# Because most callers just call .first on the result, put tables with
|
16
|
+
# Because most callers just call .first on the result, put tables with
|
17
17
|
# highest-number format first. Unsupported formats will be ignored.
|
18
|
-
@unicode ||= @tables
|
19
|
-
|
18
|
+
@unicode ||= @tables
|
19
|
+
.select { |table| table.unicode? && table.supported? }
|
20
|
+
.sort { |a, b| b.format <=> a.format }
|
20
21
|
end
|
21
22
|
|
22
23
|
private
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
25
|
+
def parse!
|
26
|
+
@version, table_count = read(4, 'nn')
|
27
|
+
@tables = []
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
end
|
29
|
+
table_count.times do
|
30
|
+
@tables << Cmap::Subtable.new(file, offset)
|
31
31
|
end
|
32
|
+
end
|
32
33
|
end
|
33
|
-
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
@@ -4,7 +4,6 @@ require_relative '../../encoding/windows_1252'
|
|
4
4
|
module TTFunk
|
5
5
|
class Table
|
6
6
|
class Cmap
|
7
|
-
|
8
7
|
module Format00
|
9
8
|
attr_reader :language
|
10
9
|
attr_reader :code_map
|
@@ -20,17 +19,17 @@ module TTFunk
|
|
20
19
|
glyph_indexes = Array.new(256, 0)
|
21
20
|
glyph_map = { 0 => 0 }
|
22
21
|
|
23
|
-
new_map = charmap.keys.sort.
|
22
|
+
new_map = charmap.keys.sort.each_with_object({}) do |code, map|
|
24
23
|
glyph_map[charmap[code]] ||= next_id += 1
|
25
|
-
map[code] = { :
|
24
|
+
map[code] = { old: charmap[code], new: glyph_map[charmap[code]] }
|
26
25
|
glyph_indexes[code] = glyph_map[charmap[code]]
|
27
26
|
map
|
28
27
|
end
|
29
28
|
|
30
29
|
# format, length, language, indices
|
31
|
-
subtable = [0, 262, 0, *glyph_indexes].pack(
|
30
|
+
subtable = [0, 262, 0, *glyph_indexes].pack('nnnC*')
|
32
31
|
|
33
|
-
{ :
|
32
|
+
{ charmap: new_map, subtable: subtable, max_glyph_id: next_id + 1 }
|
34
33
|
end
|
35
34
|
|
36
35
|
def [](code)
|
@@ -43,12 +42,11 @@ module TTFunk
|
|
43
42
|
|
44
43
|
private
|
45
44
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
45
|
+
def parse_cmap!
|
46
|
+
@language = read(4, 'x2n')
|
47
|
+
@code_map = read(256, 'C*')
|
48
|
+
end
|
50
49
|
end
|
51
|
-
|
52
50
|
end
|
53
51
|
end
|
54
52
|
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
module TTFunk
|
2
2
|
class Table
|
3
3
|
class Cmap
|
4
|
-
|
5
4
|
module Format04
|
6
5
|
attr_reader :language
|
7
6
|
attr_reader :code_map
|
@@ -19,10 +18,10 @@ module TTFunk
|
|
19
18
|
last = difference = nil
|
20
19
|
|
21
20
|
glyph_map = { 0 => 0 }
|
22
|
-
new_map = charmap.keys.sort.
|
21
|
+
new_map = charmap.keys.sort.each_with_object({}) do |code, map|
|
23
22
|
old = charmap[code]
|
24
23
|
glyph_map[old] ||= next_id += 1
|
25
|
-
map[code] = { :
|
24
|
+
map[code] = { old: old, new: glyph_map[old] }
|
26
25
|
|
27
26
|
delta = glyph_map[old] - code
|
28
27
|
if last.nil? || delta != difference
|
@@ -66,17 +65,22 @@ module TTFunk
|
|
66
65
|
end
|
67
66
|
|
68
67
|
# format, length, language
|
69
|
-
subtable = [
|
68
|
+
subtable = [
|
69
|
+
4, 16 + 8 * segcount + 2 * glyph_indices.length, 0
|
70
|
+
].pack('nnn')
|
70
71
|
|
71
|
-
search_range = 2 * 2
|
72
|
+
search_range = 2 * 2**(Math.log(segcount) / Math.log(2)).to_i
|
72
73
|
entry_selector = (Math.log(search_range / 2) / Math.log(2)).to_i
|
73
74
|
range_shift = (2 * segcount) - search_range
|
74
|
-
subtable << [
|
75
|
+
subtable << [
|
76
|
+
segcount * 2, search_range, entry_selector, range_shift
|
77
|
+
].pack('nnnn')
|
75
78
|
|
76
|
-
subtable << end_codes.pack(
|
77
|
-
subtable << deltas.pack(
|
79
|
+
subtable << end_codes.pack('n*') << "\0\0" << start_codes.pack('n*')
|
80
|
+
subtable << deltas.pack('n*') << range_offsets.pack('n*')
|
81
|
+
subtable << glyph_indices.pack('n*')
|
78
82
|
|
79
|
-
{ :
|
83
|
+
{ charmap: new_map, subtable: subtable, max_glyph_id: next_id + 1 }
|
80
84
|
end
|
81
85
|
|
82
86
|
def [](code)
|
@@ -89,38 +93,39 @@ module TTFunk
|
|
89
93
|
|
90
94
|
private
|
91
95
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
io.read(6) # skip searching hints
|
96
|
+
def parse_cmap!
|
97
|
+
length, @language, segcount_x2 = read(6, 'nnn')
|
98
|
+
segcount = segcount_x2 / 2
|
97
99
|
|
98
|
-
|
99
|
-
io.read(2) # skip reserved value
|
100
|
-
start_code = read(segcount_x2, "n*")
|
101
|
-
id_delta = read_signed(segcount)
|
102
|
-
id_range_offset = read(segcount_x2, "n*")
|
100
|
+
io.read(6) # skip searching hints
|
103
101
|
|
104
|
-
|
102
|
+
end_code = read(segcount_x2, 'n*')
|
103
|
+
io.read(2) # skip reserved value
|
104
|
+
start_code = read(segcount_x2, 'n*')
|
105
|
+
id_delta = read_signed(segcount)
|
106
|
+
id_range_offset = read(segcount_x2, 'n*')
|
105
107
|
|
106
|
-
|
108
|
+
glyph_ids = read(length - io.pos + @offset, 'n*')
|
107
109
|
|
108
|
-
|
109
|
-
start_code[i].upto(tail) do |code|
|
110
|
-
if id_range_offset[i].zero?
|
111
|
-
glyph_id = code + id_delta[i]
|
112
|
-
else
|
113
|
-
index = id_range_offset[i] / 2 + (code - start_code[i]) - (segcount - i)
|
114
|
-
glyph_id = glyph_ids[index] || 0 # because some TTF fonts are broken
|
115
|
-
glyph_id += id_delta[i] if glyph_id != 0
|
116
|
-
end
|
110
|
+
@code_map = {}
|
117
111
|
|
118
|
-
|
112
|
+
end_code.each_with_index do |tail, i|
|
113
|
+
start_code[i].upto(tail) do |code|
|
114
|
+
if id_range_offset[i] == 0
|
115
|
+
glyph_id = code + id_delta[i]
|
116
|
+
else
|
117
|
+
index = id_range_offset[i] / 2 +
|
118
|
+
(code - start_code[i]) - (segcount - i)
|
119
|
+
# Decause some TTF fonts are broken
|
120
|
+
glyph_id = glyph_ids[index] || 0
|
121
|
+
glyph_id += id_delta[i] if glyph_id != 0
|
119
122
|
end
|
123
|
+
|
124
|
+
@code_map[code] = glyph_id & 0xFFFF
|
120
125
|
end
|
121
126
|
end
|
127
|
+
end
|
122
128
|
end
|
123
|
-
|
124
129
|
end
|
125
130
|
end
|
126
131
|
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
module TTFunk
|
2
2
|
class Table
|
3
3
|
class Cmap
|
4
|
-
|
5
4
|
module Format06
|
6
5
|
attr_reader :language
|
7
6
|
attr_reader :code_map
|
@@ -11,20 +10,22 @@ module TTFunk
|
|
11
10
|
glyph_map = { 0 => 0 }
|
12
11
|
|
13
12
|
sorted_chars = charmap.keys.sort
|
14
|
-
low_char
|
15
|
-
|
13
|
+
low_char = sorted_chars.first
|
14
|
+
high_char = sorted_chars.last
|
15
|
+
entry_count = 1 + high_char - low_char
|
16
16
|
glyph_indexes = Array.new(entry_count, 0)
|
17
17
|
|
18
|
-
new_map = charmap.keys.sort.
|
18
|
+
new_map = charmap.keys.sort.each_with_object({}) do |code, map|
|
19
19
|
glyph_map[charmap[code]] ||= next_id += 1
|
20
|
-
map[code] = { :
|
20
|
+
map[code] = { old: charmap[code], new: glyph_map[charmap[code]] }
|
21
21
|
glyph_indexes[code - low_char] = glyph_map[charmap[code]]
|
22
|
-
map
|
23
22
|
end
|
24
23
|
|
25
|
-
subtable = [
|
24
|
+
subtable = [
|
25
|
+
6, 10 + entry_count * 2, 0, low_char, entry_count, *glyph_indexes
|
26
|
+
].pack('n*')
|
26
27
|
|
27
|
-
{ :
|
28
|
+
{ charmap: new_map, subtable: subtable, max_glyph_id: next_id + 1 }
|
28
29
|
end
|
29
30
|
|
30
31
|
def [](code)
|
@@ -36,16 +37,15 @@ module TTFunk
|
|
36
37
|
end
|
37
38
|
|
38
39
|
private
|
39
|
-
def parse_cmap!
|
40
|
-
@language, firstcode, entrycount = read(8, 'x2nnn')
|
41
|
-
@code_map = {}
|
42
|
-
(firstcode...(firstcode+entrycount)).each do |code|
|
43
|
-
@code_map[code] = read(2, 'n').first & 0xFFFF
|
44
|
-
end
|
45
|
-
end
|
46
40
|
|
41
|
+
def parse_cmap!
|
42
|
+
@language, firstcode, entrycount = read(8, 'x2nnn')
|
43
|
+
@code_map = {}
|
44
|
+
(firstcode...(firstcode + entrycount)).each do |code|
|
45
|
+
@code_map[code] = read(2, 'n').first & 0xFFFF
|
46
|
+
end
|
47
|
+
end
|
47
48
|
end
|
48
|
-
|
49
49
|
end
|
50
50
|
end
|
51
51
|
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
module TTFunk
|
2
2
|
class Table
|
3
3
|
class Cmap
|
4
|
-
|
5
4
|
module Format10
|
6
5
|
attr_reader :language
|
7
6
|
attr_reader :code_map
|
@@ -11,20 +10,23 @@ module TTFunk
|
|
11
10
|
glyph_map = { 0 => 0 }
|
12
11
|
|
13
12
|
sorted_chars = charmap.keys.sort
|
14
|
-
low_char
|
15
|
-
|
13
|
+
low_char = sorted_chars.first
|
14
|
+
high_char = sorted_chars.last
|
15
|
+
entry_count = 1 + high_char - low_char
|
16
16
|
glyph_indexes = Array.new(entry_count, 0)
|
17
17
|
|
18
|
-
new_map = charmap.keys.sort.
|
18
|
+
new_map = charmap.keys.sort.each_with_object({}) do |code, map|
|
19
19
|
glyph_map[charmap[code]] ||= next_id += 1
|
20
|
-
map[code] = { :
|
20
|
+
map[code] = { old: charmap[code], new: glyph_map[charmap[code]] }
|
21
21
|
glyph_indexes[code - low_char] = glyph_map[charmap[code]]
|
22
|
-
map
|
23
22
|
end
|
24
23
|
|
25
|
-
subtable = [
|
24
|
+
subtable = [
|
25
|
+
10, 0, 20 + entry_count * 4, 0, low_char, entry_count,
|
26
|
+
*glyph_indexes
|
27
|
+
].pack('nnN*')
|
26
28
|
|
27
|
-
{ :
|
29
|
+
{ charmap: new_map, subtable: subtable, max_glyph_id: next_id + 1 }
|
28
30
|
end
|
29
31
|
|
30
32
|
def [](code)
|
@@ -36,17 +38,20 @@ module TTFunk
|
|
36
38
|
end
|
37
39
|
|
38
40
|
private
|
39
|
-
def parse_cmap!
|
40
|
-
fractional_version, @language, firstcode, entrycount = read(18, 'nx4NNN')
|
41
|
-
raise NotImplementedError, "cmap version 10.#{fractional_version} is not supported" if fractional_version != 0
|
42
|
-
@code_map = {}
|
43
|
-
(firstcode...(firstcode+entrycount)).each do |code|
|
44
|
-
@code_map[code] = read(2, 'n').first & 0xFFFF
|
45
|
-
end
|
46
|
-
end
|
47
41
|
|
42
|
+
def parse_cmap!
|
43
|
+
fractional_version, @language, firstcode, entrycount =
|
44
|
+
read(18, 'nx4NNN')
|
45
|
+
if fractional_version != 0
|
46
|
+
raise NotImplementedError,
|
47
|
+
"cmap version 10.#{fractional_version} is not supported"
|
48
|
+
end
|
49
|
+
@code_map = {}
|
50
|
+
(firstcode...(firstcode + entrycount)).each do |code|
|
51
|
+
@code_map[code] = read(2, 'n').first & 0xFFFF
|
52
|
+
end
|
53
|
+
end
|
48
54
|
end
|
49
|
-
|
50
55
|
end
|
51
56
|
end
|
52
57
|
end
|