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
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
|