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
data/lib/ttfunk/table/name.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative '../table'
|
2
4
|
require 'digest/sha1'
|
3
5
|
|
4
6
|
module TTFunk
|
5
7
|
class Table
|
6
8
|
class Name < Table
|
7
|
-
class
|
9
|
+
class NameString < ::String
|
8
10
|
attr_reader :platform_id
|
9
11
|
attr_reader :encoding_id
|
10
12
|
attr_reader :language_id
|
@@ -23,6 +25,7 @@ module TTFunk
|
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
28
|
+
attr_reader :entries
|
26
29
|
attr_reader :strings
|
27
30
|
|
28
31
|
attr_reader :copyright
|
@@ -44,10 +47,30 @@ module TTFunk
|
|
44
47
|
attr_reader :compatible_full
|
45
48
|
attr_reader :sample_text
|
46
49
|
|
50
|
+
COPYRIGHT_NAME_ID = 0
|
51
|
+
FONT_FAMILY_NAME_ID = 1
|
52
|
+
FONT_SUBFAMILY_NAME_ID = 2
|
53
|
+
UNIQUE_SUBFAMILY_NAME_ID = 3
|
54
|
+
FONT_NAME_NAME_ID = 4
|
55
|
+
VERSION_NAME_ID = 5
|
56
|
+
POSTSCRIPT_NAME_NAME_ID = 6
|
57
|
+
TRADEMARK_NAME_ID = 7
|
58
|
+
MANUFACTURER_NAME_ID = 8
|
59
|
+
DESIGNER_NAME_ID = 9
|
60
|
+
DESCRIPTION_NAME_ID = 10
|
61
|
+
VENDOR_URL_NAME_ID = 11
|
62
|
+
DESIGNER_URL_NAME_ID = 12
|
63
|
+
LICENSE_NAME_ID = 13
|
64
|
+
LICENSE_URL_NAME_ID = 14
|
65
|
+
PREFERRED_FAMILY_NAME_ID = 16
|
66
|
+
PREFERRED_SUBFAMILY_NAME_ID = 17
|
67
|
+
COMPATIBLE_FULL_NAME_ID = 18
|
68
|
+
SAMPLE_TEXT_NAME_ID = 19
|
69
|
+
|
47
70
|
def self.encode(names, key = '')
|
48
71
|
tag = Digest::SHA1.hexdigest(key)[0, 6]
|
49
72
|
|
50
|
-
postscript_name =
|
73
|
+
postscript_name = NameString.new(
|
51
74
|
"#{tag}+#{names.postscript_name}", 1, 0, 0
|
52
75
|
)
|
53
76
|
|
@@ -56,23 +79,31 @@ module TTFunk
|
|
56
79
|
str_count = strings.inject(0) { |sum, (_, list)| sum + list.length }
|
57
80
|
|
58
81
|
table = [0, str_count, 6 + 12 * str_count].pack('n*')
|
59
|
-
strtable = ''
|
82
|
+
strtable = +''
|
60
83
|
|
84
|
+
items = []
|
61
85
|
strings.each do |id, list|
|
62
86
|
list.each do |string|
|
63
|
-
|
64
|
-
string.platform_id, string.encoding_id, string.language_id, id,
|
65
|
-
string.length, strtable.length
|
66
|
-
].pack('n*')
|
67
|
-
strtable << string
|
87
|
+
items << [id, string]
|
68
88
|
end
|
69
89
|
end
|
90
|
+
items = items.sort_by do |id, string|
|
91
|
+
[string.platform_id, string.encoding_id, string.language_id, id]
|
92
|
+
end
|
93
|
+
items.each do |id, string|
|
94
|
+
table << [
|
95
|
+
string.platform_id, string.encoding_id, string.language_id, id,
|
96
|
+
string.length, strtable.length
|
97
|
+
].pack('n*')
|
98
|
+
strtable << string
|
99
|
+
end
|
70
100
|
|
71
101
|
table << strtable
|
72
102
|
end
|
73
103
|
|
74
104
|
def postscript_name
|
75
105
|
return @postscript_name if @postscript_name
|
106
|
+
|
76
107
|
font_family.first || 'unnamed'
|
77
108
|
end
|
78
109
|
|
@@ -81,53 +112,60 @@ module TTFunk
|
|
81
112
|
def parse!
|
82
113
|
count, string_offset = read(6, 'x2n*')
|
83
114
|
|
84
|
-
entries = []
|
115
|
+
@entries = []
|
85
116
|
count.times do
|
86
117
|
platform, encoding, language, id, length, start_offset =
|
87
118
|
read(12, 'n*')
|
88
|
-
entries << {
|
119
|
+
@entries << {
|
89
120
|
platform_id: platform,
|
90
121
|
encoding_id: encoding,
|
91
122
|
language_id: language,
|
92
123
|
name_id: id,
|
93
124
|
length: length,
|
94
|
-
offset: offset + string_offset + start_offset
|
125
|
+
offset: offset + string_offset + start_offset,
|
126
|
+
text: nil
|
95
127
|
}
|
96
128
|
end
|
97
129
|
|
98
130
|
@strings = Hash.new { |h, k| h[k] = [] }
|
99
131
|
|
100
132
|
count.times do |i|
|
101
|
-
io.pos = entries[i][:offset]
|
102
|
-
text = io.read(entries[i][:length])
|
103
|
-
@strings[entries[i][:name_id]] <<
|
104
|
-
text,
|
105
|
-
entries[i][:platform_id],
|
106
|
-
entries[i][:encoding_id],
|
107
|
-
entries[i][:language_id]
|
133
|
+
io.pos = @entries[i][:offset]
|
134
|
+
@entries[i][:text] = io.read(@entries[i][:length])
|
135
|
+
@strings[@entries[i][:name_id]] << NameString.new(
|
136
|
+
@entries[i][:text] || '',
|
137
|
+
@entries[i][:platform_id],
|
138
|
+
@entries[i][:encoding_id],
|
139
|
+
@entries[i][:language_id]
|
108
140
|
)
|
109
141
|
end
|
110
142
|
|
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
143
|
# should only be ONE postscript name
|
118
|
-
|
119
|
-
@
|
120
|
-
@
|
121
|
-
@
|
122
|
-
@
|
123
|
-
@
|
124
|
-
@
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
144
|
+
|
145
|
+
@copyright = @strings[COPYRIGHT_NAME_ID]
|
146
|
+
@font_family = @strings[FONT_FAMILY_NAME_ID]
|
147
|
+
@font_subfamily = @strings[FONT_SUBFAMILY_NAME_ID]
|
148
|
+
@unique_subfamily = @strings[UNIQUE_SUBFAMILY_NAME_ID]
|
149
|
+
@font_name = @strings[FONT_NAME_NAME_ID]
|
150
|
+
@version = @strings[VERSION_NAME_ID]
|
151
|
+
|
152
|
+
unless @strings[POSTSCRIPT_NAME_NAME_ID].empty?
|
153
|
+
@postscript_name = @strings[POSTSCRIPT_NAME_NAME_ID]
|
154
|
+
.first.strip_extended
|
155
|
+
end
|
156
|
+
|
157
|
+
@trademark = @strings[TRADEMARK_NAME_ID]
|
158
|
+
@manufacturer = @strings[MANUFACTURER_NAME_ID]
|
159
|
+
@designer = @strings[DESIGNER_NAME_ID]
|
160
|
+
@description = @strings[DESCRIPTION_NAME_ID]
|
161
|
+
@vendor_url = @strings[VENDOR_URL_NAME_ID]
|
162
|
+
@designer_url = @strings[DESIGNER_URL_NAME_ID]
|
163
|
+
@license = @strings[LICENSE_NAME_ID]
|
164
|
+
@license_url = @strings[LICENSE_URL_NAME_ID]
|
165
|
+
@preferred_family = @strings[PREFERRED_FAMILY_NAME_ID]
|
166
|
+
@preferred_subfamily = @strings[PREFERRED_SUBFAMILY_NAME_ID]
|
167
|
+
@compatible_full = @strings[COMPATIBLE_FULL_NAME_ID]
|
168
|
+
@sample_text = @strings[SAMPLE_TEXT_NAME_ID]
|
131
169
|
end
|
132
170
|
end
|
133
171
|
end
|
data/lib/ttfunk/table/os2.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative '../table'
|
4
|
+
require 'set'
|
2
5
|
|
3
6
|
module TTFunk
|
4
7
|
class Table
|
@@ -40,16 +43,327 @@ module TTFunk
|
|
40
43
|
attr_reader :break_char
|
41
44
|
attr_reader :max_context
|
42
45
|
|
46
|
+
CODE_PAGE_BITS = {
|
47
|
+
1252 => 0, 1250 => 1, 1251 => 2, 1253 => 3, 1254 => 4,
|
48
|
+
1255 => 5, 1256 => 6, 1257 => 7, 1258 => 8, 874 => 16,
|
49
|
+
932 => 17, 936 => 18, 949 => 19, 950 => 20, 1361 => 21,
|
50
|
+
10_000 => 29, 869 => 48, 866 => 49, 865 => 50, 864 => 51,
|
51
|
+
863 => 52, 862 => 53, 861 => 54, 860 => 55, 857 => 56,
|
52
|
+
855 => 57, 852 => 58, 775 => 59, 737 => 60, 708 => 61,
|
53
|
+
850 => 62, 437 => 63
|
54
|
+
}.freeze
|
55
|
+
|
56
|
+
UNICODE_BLOCKS = {
|
57
|
+
(0x0000..0x007F) => 0, (0x0080..0x00FF) => 1,
|
58
|
+
(0x0100..0x017F) => 2, (0x0180..0x024F) => 3,
|
59
|
+
(0x0250..0x02AF) => 4, (0x1D00..0x1D7F) => 4,
|
60
|
+
(0x1D80..0x1DBF) => 4, (0x02B0..0x02FF) => 5,
|
61
|
+
(0xA700..0xA71F) => 5, (0x0300..0x036F) => 6,
|
62
|
+
(0x1DC0..0x1DFF) => 6, (0x0370..0x03FF) => 7,
|
63
|
+
(0x2C80..0x2CFF) => 8, (0x0400..0x04FF) => 9,
|
64
|
+
(0x0500..0x052F) => 9, (0x2DE0..0x2DFF) => 9,
|
65
|
+
(0xA640..0xA69F) => 9, (0x0530..0x058F) => 10,
|
66
|
+
(0x0590..0x05FF) => 11, (0xA500..0xA63F) => 12,
|
67
|
+
(0x0600..0x06FF) => 13, (0x0750..0x077F) => 13,
|
68
|
+
(0x07C0..0x07FF) => 14, (0x0900..0x097F) => 15,
|
69
|
+
(0x0980..0x09FF) => 16, (0x0A00..0x0A7F) => 17,
|
70
|
+
(0x0A80..0x0AFF) => 18, (0x0B00..0x0B7F) => 19,
|
71
|
+
(0x0B80..0x0BFF) => 20, (0x0C00..0x0C7F) => 21,
|
72
|
+
(0x0C80..0x0CFF) => 22, (0x0D00..0x0D7F) => 23,
|
73
|
+
(0x0E00..0x0E7F) => 24, (0x0E80..0x0EFF) => 25,
|
74
|
+
(0x10A0..0x10FF) => 26, (0x2D00..0x2D2F) => 26,
|
75
|
+
(0x1B00..0x1B7F) => 27, (0x1100..0x11FF) => 28,
|
76
|
+
(0x1E00..0x1EFF) => 29, (0x2C60..0x2C7F) => 29,
|
77
|
+
(0xA720..0xA7FF) => 29, (0x1F00..0x1FFF) => 30,
|
78
|
+
(0x2000..0x206F) => 31, (0x2E00..0x2E7F) => 31,
|
79
|
+
(0x2070..0x209F) => 32, (0x20A0..0x20CF) => 33,
|
80
|
+
(0x20D0..0x20FF) => 34, (0x2100..0x214F) => 35,
|
81
|
+
(0x2150..0x218F) => 36, (0x2190..0x21FF) => 37,
|
82
|
+
(0x27F0..0x27FF) => 37, (0x2900..0x297F) => 37,
|
83
|
+
(0x2B00..0x2BFF) => 37, (0x2200..0x22FF) => 38,
|
84
|
+
(0x2A00..0x2AFF) => 38, (0x27C0..0x27EF) => 38,
|
85
|
+
(0x2980..0x29FF) => 38, (0x2300..0x23FF) => 39,
|
86
|
+
(0x2400..0x243F) => 40, (0x2440..0x245F) => 41,
|
87
|
+
(0x2460..0x24FF) => 42, (0x2500..0x257F) => 43,
|
88
|
+
(0x2580..0x259F) => 44, (0x25A0..0x25FF) => 45,
|
89
|
+
(0x2600..0x26FF) => 46, (0x2700..0x27BF) => 47,
|
90
|
+
(0x3000..0x303F) => 48, (0x3040..0x309F) => 49,
|
91
|
+
(0x30A0..0x30FF) => 50, (0x31F0..0x31FF) => 50,
|
92
|
+
(0x3100..0x312F) => 51, (0x31A0..0x31BF) => 51,
|
93
|
+
(0x3130..0x318F) => 52, (0xA840..0xA87F) => 53,
|
94
|
+
(0x3200..0x32FF) => 54, (0x3300..0x33FF) => 55,
|
95
|
+
(0xAC00..0xD7AF) => 56, (0xD800..0xDFFF) => 57,
|
96
|
+
(0x10900..0x1091F) => 58, (0x4E00..0x9FFF) => 59,
|
97
|
+
(0x2E80..0x2EFF) => 59, (0x2F00..0x2FDF) => 59,
|
98
|
+
(0x2FF0..0x2FFF) => 59, (0x3400..0x4DBF) => 59,
|
99
|
+
(0x20000..0x2A6DF) => 59, (0x3190..0x319F) => 59,
|
100
|
+
(0xE000..0xF8FF) => 60, (0x31C0..0x31EF) => 61,
|
101
|
+
(0xF900..0xFAFF) => 61, (0x2F800..0x2FA1F) => 61,
|
102
|
+
(0xFB00..0xFB4F) => 62, (0xFB50..0xFDFF) => 63,
|
103
|
+
(0xFE20..0xFE2F) => 64, (0xFE10..0xFE1F) => 65,
|
104
|
+
(0xFE30..0xFE4F) => 65, (0xFE50..0xFE6F) => 66,
|
105
|
+
(0xFE70..0xFEFF) => 67, (0xFF00..0xFFEF) => 68,
|
106
|
+
(0xFFF0..0xFFFF) => 69, (0x0F00..0x0FFF) => 70,
|
107
|
+
(0x0700..0x074F) => 71, (0x0780..0x07BF) => 72,
|
108
|
+
(0x0D80..0x0DFF) => 73, (0x1000..0x109F) => 74,
|
109
|
+
(0x1200..0x137F) => 75, (0x1380..0x139F) => 75,
|
110
|
+
(0x2D80..0x2DDF) => 75, (0x13A0..0x13FF) => 76,
|
111
|
+
(0x1400..0x167F) => 77, (0x1680..0x169F) => 78,
|
112
|
+
(0x16A0..0x16FF) => 79, (0x1780..0x17FF) => 80,
|
113
|
+
(0x19E0..0x19FF) => 80, (0x1800..0x18AF) => 81,
|
114
|
+
(0x2800..0x28FF) => 82, (0xA000..0xA48F) => 83,
|
115
|
+
(0xA490..0xA4CF) => 83, (0x1700..0x171F) => 84,
|
116
|
+
(0x1720..0x173F) => 84, (0x1740..0x175F) => 84,
|
117
|
+
(0x1760..0x177F) => 84, (0x10300..0x1032F) => 85,
|
118
|
+
(0x10330..0x1034F) => 86, (0x10400..0x1044F) => 87,
|
119
|
+
(0x1D000..0x1D0FF) => 88, (0x1D100..0x1D1FF) => 88,
|
120
|
+
(0x1D200..0x1D24F) => 88, (0x1D400..0x1D7FF) => 89,
|
121
|
+
(0xF0000..0xFFFFD) => 90, (0x100000..0x10FFFD) => 90,
|
122
|
+
(0xFE00..0xFE0F) => 91, (0xE0100..0xE01EF) => 91,
|
123
|
+
(0xE0000..0xE007F) => 92, (0x1900..0x194F) => 93,
|
124
|
+
(0x1950..0x197F) => 94, (0x1980..0x19DF) => 95,
|
125
|
+
(0x1A00..0x1A1F) => 96, (0x2C00..0x2C5F) => 97,
|
126
|
+
(0x2D30..0x2D7F) => 98, (0x4DC0..0x4DFF) => 99,
|
127
|
+
(0xA800..0xA82F) => 100, (0x10000..0x1007F) => 101,
|
128
|
+
(0x10080..0x100FF) => 101, (0x10100..0x1013F) => 101,
|
129
|
+
(0x10140..0x1018F) => 102, (0x10380..0x1039F) => 103,
|
130
|
+
(0x103A0..0x103DF) => 104, (0x10450..0x1047F) => 105,
|
131
|
+
(0x10480..0x104AF) => 106, (0x10800..0x1083F) => 107,
|
132
|
+
(0x10A00..0x10A5F) => 108, (0x1D300..0x1D35F) => 109,
|
133
|
+
(0x12000..0x123FF) => 110, (0x12400..0x1247F) => 110,
|
134
|
+
(0x1D360..0x1D37F) => 111, (0x1B80..0x1BBF) => 112,
|
135
|
+
(0x1C00..0x1C4F) => 113, (0x1C50..0x1C7F) => 114,
|
136
|
+
(0xA880..0xA8DF) => 115, (0xA900..0xA92F) => 116,
|
137
|
+
(0xA930..0xA95F) => 117, (0xAA00..0xAA5F) => 118,
|
138
|
+
(0x10190..0x101CF) => 119, (0x101D0..0x101FF) => 120,
|
139
|
+
(0x102A0..0x102DF) => 121, (0x10280..0x1029F) => 121,
|
140
|
+
(0x10920..0x1093F) => 121, (0x1F030..0x1F09F) => 122,
|
141
|
+
(0x1F000..0x1F02F) => 122
|
142
|
+
}.freeze
|
143
|
+
|
144
|
+
UNICODE_MAX = 0xFFFF
|
145
|
+
UNICODE_RANGES = UNICODE_BLOCKS.keys.freeze
|
146
|
+
LOWERCASE_START = 'a'.ord
|
147
|
+
LOWERCASE_END = 'z'.ord
|
148
|
+
LOWERCASE_COUNT = (LOWERCASE_END - LOWERCASE_START) + 1
|
149
|
+
CODEPOINT_SPACE = 32
|
150
|
+
SPACE_GLYPH_MISSING_ERROR = "Space glyph (0x#{CODEPOINT_SPACE.to_s(16)})"\
|
151
|
+
' must be included in the font'
|
152
|
+
|
153
|
+
# Used to calculate the xAvgCharWidth field.
|
154
|
+
# From https://docs.microsoft.com/en-us/typography/opentype/spec/os2:
|
155
|
+
#
|
156
|
+
# "When first defined, the specification was biased toward Basic Latin
|
157
|
+
# characters, and it was thought that the xAvgCharWidth value could be
|
158
|
+
# used to estimate the average length of lines of text. A formula for
|
159
|
+
# calculating xAvgCharWidth was provided using frequency-of-use
|
160
|
+
# weighting factors for lowercase letters a - z."
|
161
|
+
#
|
162
|
+
# The array below contains 26 weight values which correspond to the
|
163
|
+
# 26 letters in the Latin alphabet. Each weight is the relative
|
164
|
+
# frequency of that letter in the English language.
|
165
|
+
WEIGHT_SPACE = 166
|
166
|
+
WEIGHT_LOWERCASE = [
|
167
|
+
64, 14, 27, 35, 100, 20, 14, 42, 63, 3, 6, 35, 20,
|
168
|
+
56, 56, 17, 4, 49, 56, 71, 31, 10, 18, 3, 18, 2
|
169
|
+
].freeze
|
170
|
+
|
43
171
|
def tag
|
44
172
|
'OS/2'
|
45
173
|
end
|
46
174
|
|
175
|
+
class << self
|
176
|
+
def encode(os2, subset)
|
177
|
+
''.b.tap do |result|
|
178
|
+
result << [
|
179
|
+
os2.version, avg_char_width_for(os2, subset), os2.weight_class,
|
180
|
+
os2.width_class, os2.type, os2.y_subscript_x_size,
|
181
|
+
os2.y_subscript_y_size, os2.y_subscript_x_offset,
|
182
|
+
os2.y_subscript_y_offset, os2.y_superscript_x_size,
|
183
|
+
os2.y_superscript_y_size, os2.y_superscript_x_offset,
|
184
|
+
os2.y_superscript_y_offset, os2.y_strikeout_size,
|
185
|
+
os2.y_strikeout_position, os2.family_class
|
186
|
+
].pack('n*')
|
187
|
+
|
188
|
+
result << os2.panose
|
189
|
+
|
190
|
+
new_char_range = unicode_blocks_for(os2, os2.char_range, subset)
|
191
|
+
result << BinUtils
|
192
|
+
.slice_int(
|
193
|
+
new_char_range.value,
|
194
|
+
bit_width: 32,
|
195
|
+
slice_count: 4
|
196
|
+
)
|
197
|
+
.pack('N*')
|
198
|
+
|
199
|
+
result << os2.vendor_id
|
200
|
+
|
201
|
+
new_cmap_table = subset.new_cmap_table[:charmap]
|
202
|
+
code_points = new_cmap_table
|
203
|
+
.keys
|
204
|
+
.select { |k| new_cmap_table[k][:new] > 0 }
|
205
|
+
.sort
|
206
|
+
|
207
|
+
# "This value depends on which character sets the font supports.
|
208
|
+
# This field cannot represent supplementary character values
|
209
|
+
# (codepoints greater than 0xFFFF). Fonts that support
|
210
|
+
# supplementary characters should set the value in this field
|
211
|
+
# to 0xFFFF."
|
212
|
+
first_char_index = [code_points.first || 0, UNICODE_MAX].min
|
213
|
+
last_char_index = [code_points.last || 0, UNICODE_MAX].min
|
214
|
+
|
215
|
+
result << [
|
216
|
+
os2.selection, first_char_index, last_char_index
|
217
|
+
].pack('n*')
|
218
|
+
|
219
|
+
if os2.version > 0
|
220
|
+
result << [
|
221
|
+
os2.ascent, os2.descent, os2.line_gap,
|
222
|
+
os2.win_ascent, os2.win_descent
|
223
|
+
].pack('n*')
|
224
|
+
|
225
|
+
result << BinUtils
|
226
|
+
.slice_int(
|
227
|
+
code_pages_for(subset).value,
|
228
|
+
bit_width: 32,
|
229
|
+
slice_count: 2
|
230
|
+
)
|
231
|
+
.pack('N*')
|
232
|
+
|
233
|
+
if os2.version > 1
|
234
|
+
result << [
|
235
|
+
os2.x_height, os2.cap_height, os2.default_char,
|
236
|
+
os2.break_char, os2.max_context
|
237
|
+
].pack('n*')
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
private
|
244
|
+
|
245
|
+
def code_pages_for(subset)
|
246
|
+
field = BitField.new(0)
|
247
|
+
return field if subset.unicode?
|
248
|
+
|
249
|
+
field.on(CODE_PAGE_BITS[subset.code_page])
|
250
|
+
field
|
251
|
+
end
|
252
|
+
|
253
|
+
def unicode_blocks_for(os2, original_field, subset)
|
254
|
+
field = BitField.new(0)
|
255
|
+
return field unless subset.unicode?
|
256
|
+
|
257
|
+
subset_code_points = Set.new(subset.new_cmap_table[:charmap].keys)
|
258
|
+
original_code_point_groups = group_original_code_points_by_bit(os2)
|
259
|
+
|
260
|
+
original_code_point_groups.each do |bit, code_points|
|
261
|
+
next if original_field.off?(bit)
|
262
|
+
|
263
|
+
if code_points.any? { |cp| subset_code_points.include?(cp) }
|
264
|
+
field.on(bit)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
field
|
269
|
+
end
|
270
|
+
|
271
|
+
def group_original_code_points_by_bit(os2)
|
272
|
+
Hash.new { |h, k| h[k] = [] }.tap do |result|
|
273
|
+
os2.file.cmap.unicode.first.code_map.each_key do |code_point|
|
274
|
+
# find corresponding bit
|
275
|
+
range = UNICODE_RANGES.find { |r| r.cover?(code_point) }
|
276
|
+
|
277
|
+
if (bit = UNICODE_BLOCKS[range])
|
278
|
+
result[bit] << code_point
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def avg_char_width_for(os2, subset)
|
285
|
+
if subset.microsoft_symbol?
|
286
|
+
avg_ms_symbol_char_width_for(os2, subset)
|
287
|
+
else
|
288
|
+
avg_weighted_char_width_for(os2, subset)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def avg_ms_symbol_char_width_for(os2, subset)
|
293
|
+
total_width = 0
|
294
|
+
num_glyphs = 0
|
295
|
+
|
296
|
+
# use new -> old glyph mapping in order to include compound glyphs
|
297
|
+
# in the calculation
|
298
|
+
subset.new_to_old_glyph.each do |_, old_gid|
|
299
|
+
if (metric = os2.file.horizontal_metrics.for(old_gid))
|
300
|
+
total_width += metric.advance_width
|
301
|
+
num_glyphs += 1 if metric.advance_width > 0
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
return 0 if num_glyphs == 0
|
306
|
+
|
307
|
+
total_width / num_glyphs # this should be a whole number
|
308
|
+
end
|
309
|
+
|
310
|
+
def avg_weighted_char_width_for(os2, subset)
|
311
|
+
# make sure the subset includes the space char
|
312
|
+
unless subset.to_unicode_map[CODEPOINT_SPACE]
|
313
|
+
raise SPACE_GLYPH_MISSING_ERROR
|
314
|
+
end
|
315
|
+
|
316
|
+
space_gid = os2.file.cmap.unicode.first[CODEPOINT_SPACE]
|
317
|
+
space_hm = os2.file.horizontal_metrics.for(space_gid)
|
318
|
+
return 0 unless space_hm
|
319
|
+
|
320
|
+
total_weight = space_hm.advance_width * WEIGHT_SPACE
|
321
|
+
num_lowercase = 0
|
322
|
+
|
323
|
+
# calculate the weighted sum of all the lowercase widths in
|
324
|
+
# the subset
|
325
|
+
LOWERCASE_START.upto(LOWERCASE_END) do |lowercase_cp|
|
326
|
+
# make sure the subset includes the character
|
327
|
+
next unless subset.to_unicode_map[lowercase_cp]
|
328
|
+
|
329
|
+
lowercase_gid = os2.file.cmap.unicode.first[lowercase_cp]
|
330
|
+
lowercase_hm = os2.file.horizontal_metrics.for(lowercase_gid)
|
331
|
+
|
332
|
+
num_lowercase += 1
|
333
|
+
total_weight += lowercase_hm.advance_width *
|
334
|
+
WEIGHT_LOWERCASE[lowercase_cp - 'a'.ord]
|
335
|
+
end
|
336
|
+
|
337
|
+
# return if all lowercase characters are present in the subset
|
338
|
+
return total_weight / 1000 if num_lowercase == LOWERCASE_COUNT
|
339
|
+
|
340
|
+
# If not all lowercase characters are present in the subset, take
|
341
|
+
# the average width of all the subsetted characters. This differs
|
342
|
+
# from avg_ms_char_width_for in that it includes zero-width glyphs
|
343
|
+
# in the calculation.
|
344
|
+
total_width = 0
|
345
|
+
num_glyphs = subset.new_to_old_glyph.size
|
346
|
+
|
347
|
+
# use new -> old glyph mapping in order to include compound glyphs
|
348
|
+
# in the calculation
|
349
|
+
subset.new_to_old_glyph.each do |_, old_gid|
|
350
|
+
if (metric = os2.file.horizontal_metrics.for(old_gid))
|
351
|
+
total_width += metric.advance_width
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
return 0 if num_glyphs == 0
|
356
|
+
|
357
|
+
total_width / num_glyphs # this should be a whole number
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
47
361
|
private
|
48
362
|
|
49
363
|
def parse!
|
50
364
|
@version = read(2, 'n').first
|
51
365
|
|
52
|
-
@ave_char_width = read_signed(1)
|
366
|
+
@ave_char_width = read_signed(1).first
|
53
367
|
@weight_class, @width_class = read(4, 'nn')
|
54
368
|
@type, @y_subscript_x_size, @y_subscript_y_size, @y_subscript_x_offset,
|
55
369
|
@y_subscript_y_offset, @y_superscript_x_size, @y_superscript_y_size,
|
@@ -57,19 +371,28 @@ module TTFunk
|
|
57
371
|
@y_strikeout_position, @family_class = read_signed(12)
|
58
372
|
@panose = io.read(10)
|
59
373
|
|
60
|
-
@char_range =
|
61
|
-
|
374
|
+
@char_range = BitField.new(
|
375
|
+
BinUtils.stitch_int(read(16, 'N*'), bit_width: 32)
|
376
|
+
)
|
62
377
|
|
378
|
+
@vendor_id = io.read(4)
|
63
379
|
@selection, @first_char_index, @last_char_index = read(6, 'n*')
|
64
380
|
|
65
381
|
if @version > 0
|
66
382
|
@ascent, @descent, @line_gap = read_signed(3)
|
67
383
|
@win_ascent, @win_descent = read(4, 'nn')
|
68
|
-
@code_page_range =
|
384
|
+
@code_page_range = BitField.new(
|
385
|
+
BinUtils.stitch_int(read(8, 'N*'), bit_width: 32)
|
386
|
+
)
|
69
387
|
|
70
388
|
if @version > 1
|
71
389
|
@x_height, @cap_height = read_signed(2)
|
72
390
|
@default_char, @break_char, @max_context = read(6, 'nnn')
|
391
|
+
|
392
|
+
# Set this to zero until GSUB/GPOS support has been implemented.
|
393
|
+
# This value is calculated via those tables, and should be set to
|
394
|
+
# zero if the data is not available.
|
395
|
+
@max_context = 0
|
73
396
|
end
|
74
397
|
end
|
75
398
|
end
|