1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
|
local unicode_data = require'luaotfload-unicode'
local has_accent = 1
local has_combining_dialytika = 2
local has_ypogegrammeni = 4
local has_other_diacritic = 8
local has_vowel = 16
local vowels = {
[0x03B1] = true,
[0x0391] = true,
[0x03B5] = true,
[0x0395] = true,
[0x03B7] = true,
[0x0397] = true,
[0x03B9] = true,
[0x0399] = true,
[0x03BF] = true,
[0x039F] = true,
[0x03C9] = true,
[0x03A9] = true,
[0x03C5] = true,
[0x03A5] = true,
[0x03F5] = true,
}
local diacritic_data = {
[0x0300] = has_accent,
[0x0301] = has_accent,
[0x0302] = has_accent,
[0x0303] = has_accent,
[0x0311] = has_accent,
[0x0342] = has_accent,
[0x0308] = has_combining_dialytika,
[0x0344] = has_accent | has_combining_dialytika,
[0x0345] = has_ypogegrammeni,
[0x0304] = has_other_diacritic,
[0x0306] = has_other_diacritic,
[0x0313] = has_other_diacritic,
[0x0314] = has_other_diacritic,
[0x0343] = has_other_diacritic,
}
local mapping_tables = unicode_data.casemapping
local soft_dotted = unicode_data.soft_dotted
local ccc = unicode_data.ccc
local uppercase = mapping_tables.uppercase
local lowercase = mapping_tables.lowercase
local cased = mapping_tables.cased
local case_ignorable = mapping_tables.case_ignorable
local otfregister = fonts.constructors.features.otf.register
local direct = node.direct
local is_char = direct.is_char
local has_glyph = direct.has_glyph
local uses_font = direct.uses_font
local getnext = direct.getnext
local setchar = direct.setchar
local setdisc = direct.setdisc
local getdisc = direct.getdisc
local getfield = direct.getfield
local remove = direct.remove
local copy = direct.copy
local insert_after = direct.insert_after
local traverse = direct.traverse
local disc = node.id'disc'
local nfd = {} -- FIXME: Data
local data do
for cp = 1, 0xFF, 1 do -- FIXME: Correct limits
local decomposed = nfd[cp]
local first = decomposed and decomposed[1] or cp
local upper = uppercase[first]
local data = vowels[first] and has_vowel or 0
if decomposed then
for i=1, #decomposed do
data = data | (diacritic_data[decomposed[i]] or 0)
end
end
end
end
--[[ We make some implicit assumptions about contexts in SpecialCasing.txt here which happened to be true when I wrote the code:
--
-- * Before_Dot only appears as Not_Before_Dot
-- * No other context appears with Not_
-- * Final_Sigma is never language dependent
-- * Other contexts are always language dependent
-- * The only languages with special mappings are Lithuanian (lt/"LTH "/lit), Turkish (tr/"TRK "/tur), and Azeri/Azerbaijani (az/"AZE "/aze)
]]
local font_lang = setmetatable({}, {__index = function(t, fid)
local f = font.getfont(fid)
local lang = f.specification.features.normal.language
lang = lang == 'lth' and 'lt' or lang == 'trk' and 'tr' or lang == 'aze' and 'az' or false
t[fid] = lang
return lang
end})
local function is_Final_Sigma(font, mapping, n, after)
mapping = mapping.Final_Sigma
if not mapping then return false end
mapping = mapping._
if not mapping then return false end
n = getnext(n)
repeat
while n do
local char, id = is_char(n, font)
if id == disc then
after = getnext(n)
n = getfield(n, 'replace')
char, id = is_char(n, font)
elseif char then
if not case_ignorable[char] then
return not cased[char] and mapping
end
n = getnext(n)
else
return mapping
end
end
n, after = after
until not n
return mapping
end
local function is_More_Above(font, mapping, n, after)
mapping = mapping.More_Above
if not mapping then return false end
mapping = mapping._
if not mapping then return false end
n = getnext(n)
repeat
while n do
local char, id = is_char(n, font)
if id == disc then
after = getnext(n)
n = getfield(n, 'replace')
char, id = is_char(n, font)
elseif char then
local char_ccc = ccc[char]
if not char_ccc then
return false
elseif char_ccc == 230 then
return mapping
end
n = getnext(n)
else
return false
end
end
n, after = after
until not n
return false
end
local function is_Not_Before_Dot(font, mapping, n, after)
mapping = mapping.Not_Before_Dot
if not mapping then return false end
mapping = mapping._
if not mapping then return false end
n = getnext(n)
repeat
while n do
local char, id = is_char(n, font)
if id == disc then
after = getnext(n)
n = getfield(n, 'replace')
char, id = is_char(n, font)
elseif char then
local char_ccc = ccc[char]
if not char_ccc then
return mapping
elseif char_ccc == 230 then
return char ~= 0x0307 and mapping
end
n = getnext(n)
else
return mapping
end
end
n, after = after
until not n
return mapping
end
local function is_Language_Mapping(font, mapping, n, after, seen_soft_dotted, seen_I)
if not mapping then return false end
if seen_soft_dotted then
local mapping = mapping.After_Soft_Dotted
mapping = mapping and mapping._
if mapping then
return mapping
end
end
if seen_I then
local mapping = mapping.After_I
mapping = mapping and mapping._
if mapping then
return mapping
end
end
return is_More_Above(font, mapping, n, after) or is_Not_Before_Dot(font, mapping, n, after) or mapping._ -- Might be nil
end
local function process(table)
local function processor(head, font, after, seen_cased, seen_soft_dotted, seen_I)
local lang = font_lang[font]
local n = head
while n do
do
local new = has_glyph(n)
if n ~= new then
seen_cased, seen_soft_dotted, seen_I = nil
end
n = new
end
if not n then break end
local char, id = is_char(n, font)
if char then
local mapping = table[char]
if mapping then
if tonumber(mapping) then
setchar(n, mapping)
else
mapping = seen_cased and is_Final_Sigma(font, mapping, n, after)
or lang and is_Language_Mapping(font, mapping[lang], n, after, seen_soft_dotted, seen_I)
or mapping._
if #mapping == 0 then
head, n = remove(head, n)
goto continue
else
setchar(n, mapping[1])
for i=2, #mapping do
head, n = insert_after(head, n, copy(n))
setchar(n, mapping[i])
end
end
end
end
if not case_ignorable[char] then
seen_cased = cased[char] or nil
end
local char_ccc = ccc[char]
if not char_ccc or char_ccc == 230 then
seen_I = char == 0x49 or nil
seen_soft_dotted = soft_dotted[char]
end
elseif id == disc and uses_font(n, font) then
local pre, post, rep = getdisc(n)
local after = getnext(n)
pre, post, rep, seen_cased, seen_soft_dotted, seen_I =
processor(pre, font, nil, seen_cased, seen_soft_dotted, seen_I),
processor(post, font, after),
processor(rep, font, after, seen_cased, seen_soft_dotted, seen_I)
setdisc(n, pre, post, rep)
else
seen_cased, seen_soft_dotted, seen_I = nil
end
n = getnext(n)
::continue::
end
return head, seen_cased, seen_soft_dotted, seen_I
end
return function(head, font) return (processor(head, font)) end
end
local upper_process = process(uppercase)
otfregister {
name = 'upper',
description = 'Map to uppercase',
default = false,
processors = {
position = 1,
plug = upper_process,
node = upper_process,
base = upper_process,
},
}
local lower_process = process(lowercase)
otfregister {
name = 'lower',
description = 'Map to lowercase',
default = false,
processors = {
position = 1,
plug = lower_process,
node = lower_process,
base = lower_process,
},
}
|