1if not modules then modules = { } end modules ['font-oto'] = {
2 version = 1.001,
3 comment = "companion to font-ini.mkiv",
4 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5 copyright = "PRAGMA ADE / ConTeXt Development Team",
6 license = "see context related readme files"
7}
8
9local concat, unpack = table.concat, table.unpack
10local insert, remove = table.insert, table.remove
11local format, gmatch, gsub, find, match, lower, strip = string.format, string.gmatch, string.gsub, string.find, string.match, string.lower, string.strip
12local type, next, tonumber, tostring = type, next, tonumber, tostring
13
14local trace_baseinit = false trackers.register("otf.baseinit", function(v) trace_baseinit = v end)
15local trace_singles = false trackers.register("otf.singles", function(v) trace_singles = v end)
16local trace_multiples = false trackers.register("otf.multiples", function(v) trace_multiples = v end)
17local trace_alternatives = false trackers.register("otf.alternatives", function(v) trace_alternatives = v end)
18local trace_ligatures = false trackers.register("otf.ligatures", function(v) trace_ligatures = v end)
19local trace_kerns = false trackers.register("otf.kerns", function(v) trace_kerns = v end)
20local trace_preparing = false trackers.register("otf.preparing", function(v) trace_preparing = v end)
21
22local report_prepare = logs.reporter("fonts","otf prepare")
23
24local fonts = fonts
25local otf = fonts.handlers.otf
26
27local otffeatures = otf.features
28local registerotffeature = otffeatures.register
29
30otf.defaultbasealternate = "none"
31
32local getprivate = fonts.constructors.getprivate
33
34local wildcard <const> = "*"
35local default <const> = "dflt"
36
37local formatters = string.formatters
38local f_unicode = formatters["%U"]
39local f_uniname = formatters["%U (%s)"]
40local f_unilist = formatters["% t (% t)"]
41
42local function gref(descriptions,n)
43 if type(n) == "number" then
44 local name = descriptions[n].name
45 if name then
46 return f_uniname(n,name)
47 else
48 return f_unicode(n)
49 end
50 elseif n then
51 local num = { }
52 local nam = { }
53 local j = 0
54 for i=1,#n do
55 local ni = n[i]
56 if tonumber(ni) then
57 j = j + 1
58 local di = descriptions[ni]
59 num[j] = f_unicode(ni)
60 nam[j] = di and di.name or "-"
61 end
62 end
63 return f_unilist(num,nam)
64 else
65 return "<error in base mode tracing>"
66 end
67end
68
69local function cref(feature,sequence)
70 return formatters["feature %a, type %a, (chain) lookup %a"](feature,sequence.type,sequence.name)
71end
72
73local function report_substitution(feature,sequence,descriptions,unicode,substitution)
74 if unicode == substitution then
75 report_prepare("%s: base substitution %s maps onto itself",
76 cref(feature,sequence),
77 gref(descriptions,unicode))
78 else
79 report_prepare("%s: base substitution %s => %S",
80 cref(feature,sequence),
81 gref(descriptions,unicode),
82 gref(descriptions,substitution))
83 end
84end
85
86local function report_alternate(feature,sequence,descriptions,unicode,replacement,value,comment)
87 if unicode == replacement then
88 report_prepare("%s: base alternate %s maps onto itself",
89 cref(feature,sequence),
90 gref(descriptions,unicode))
91 else
92 report_prepare("%s: base alternate %s => %s (%S => %S)",
93 cref(feature,sequence),
94 gref(descriptions,unicode),
95 replacement and gref(descriptions,replacement),
96 value,
97 comment)
98 end
99end
100
101local function report_ligature(feature,sequence,descriptions,unicode,ligature)
102 report_prepare("%s: base ligature %s => %S",
103 cref(feature,sequence),
104 gref(descriptions,ligature),
105 gref(descriptions,unicode))
106end
107
108local function report_kern(feature,sequence,descriptions,unicode,otherunicode,value)
109 report_prepare("%s: base kern %s + %s => %S",
110 cref(feature,sequence),
111 gref(descriptions,unicode),
112 gref(descriptions,otherunicode),
113 value)
114end
115
116
117
118
119
120
121local basehash, basehashes, applied = { }, 1, { }
122
123local function registerbasehash(tfmdata)
124 local properties = tfmdata.properties
125 local hash = concat(applied," ")
126 local base = basehash[hash]
127 if not base then
128 basehashes = basehashes + 1
129 base = basehashes
130 basehash[hash] = base
131 end
132 properties.basehash = base
133 properties.fullname = (properties.fullname or properties.name) .. "-" .. base
134
135 applied = { }
136end
137
138local function registerbasefeature(feature,value)
139 applied[#applied+1] = feature .. "=" .. tostring(value)
140end
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
170local function makefake(tfmdata,name,present)
171 local private = getprivate(tfmdata)
172 local character = { intermediate = true, ligatures = { } }
173 tfmdata.resources.unicodes[name] = private
174 tfmdata.characters[private] = character
175 tfmdata.descriptions[private] = { name = name }
176 present[name] = private
177 return character
178end
179
180local function make_1(present,tree,name)
181 if tonumber(tree) then
182 present[name] = v
183 else
184 for k, v in next, tree do
185 if k == "ligature" then
186 present[name] = v
187 else
188 make_1(present,v,name .. "_" .. k)
189 end
190 end
191 end
192end
193
194local function make_3(present,tfmdata,characters,tree,name,preceding,unicode,done,v)
195 local character = characters[preceding]
196 if not character then
197 if trace_baseinit then
198 report_prepare("weird ligature in lookup %a, current %C, preceding %C",sequence.name,v,preceding)
199 end
200 character = makefake(tfmdata,name,present)
201 end
202 local ligatures = character.ligatures
203 if ligatures then
204 ligatures[unicode] = { char = v }
205 else
206 character.ligatures = { [unicode] = { char = v } }
207 end
208 if done then
209 local d = done[name]
210 if not d then
211 done[name] = { "dummy", v }
212 else
213 d[#d+1] = v
214 end
215 end
216end
217
218local function make_2(present,tfmdata,characters,tree,name,preceding,unicode,done)
219 if tonumber(tree) then
220 make_3(present,tfmdata,characters,tree,name,preceding,unicode,done,tree)
221 else
222 for k, v in next, tree do
223 if k == "ligature" then
224 make_3(present,tfmdata,characters,tree,name,preceding,unicode,done,v)
225 else
226 local code = present[name] or unicode
227 local name = name .. "_" .. k
228 make_2(present,tfmdata,characters,v,name,code,k,done)
229 end
230 end
231 end
232end
233
234local function preparesubstitutions(tfmdata,feature,value,validlookups,lookuplist)
235 local characters = tfmdata.characters
236 local descriptions = tfmdata.descriptions
237 local resources = tfmdata.resources
238 local changed = tfmdata.changed
239
240 local ligatures = { }
241 local alternate = tonumber(value) or true and 1
242 local defaultalt = otf.defaultbasealternate
243 local trace_singles = trace_baseinit and trace_singles
244 local trace_alternatives = trace_baseinit and trace_alternatives
245 local trace_ligatures = trace_baseinit and trace_ligatures
246
247
248
249
250 if not changed then
251 changed = { }
252 tfmdata.changed = changed
253 end
254
255 for i=1,#lookuplist do
256 local sequence = lookuplist[i]
257 local steps = sequence.steps
258 local kind = sequence.type
259 if kind == "gsub_single" then
260 for i=1,#steps do
261 for unicode, data in next, steps[i].coverage do
262 if unicode ~= data and not changed[unicode] then
263 changed[unicode] = data
264 end
265 if trace_singles then
266 report_substitution(feature,sequence,descriptions,unicode,data)
267 end
268 end
269 end
270 elseif kind == "gsub_alternate" then
271 for i=1,#steps do
272 for unicode, data in next, steps[i].coverage do
273 local replacement = data[alternate]
274 if replacement then
275 if unicode ~= replacement and not changed[unicode] then
276 changed[unicode] = replacement
277 end
278 if trace_alternatives then
279 report_alternate(feature,sequence,descriptions,unicode,replacement,value,"normal")
280 end
281 elseif defaultalt == "first" then
282 replacement = data[1]
283 if unicode ~= replacement and not changed[unicode] then
284 changed[unicode] = replacement
285 end
286 if trace_alternatives then
287 report_alternate(feature,sequence,descriptions,unicode,replacement,value,defaultalt)
288 end
289 elseif defaultalt == "last" then
290 replacement = data[#data]
291 if unicode ~= replacement and not changed[unicode] then
292 changed[unicode] = replacement
293 end
294 if trace_alternatives then
295 report_alternate(feature,sequence,descriptions,unicode,replacement,value,defaultalt)
296 end
297 else
298 if trace_alternatives then
299 report_alternate(feature,sequence,descriptions,unicode,replacement,value,"unknown")
300 end
301 end
302 end
303 end
304 elseif kind == "gsub_ligature" then
305 for i=1,#steps do
306 for unicode, data in next, steps[i].coverage do
307 ligatures[#ligatures+1] = { unicode, data, "" }
308 if trace_ligatures then
309 report_ligature(feature,sequence,descriptions,unicode,data)
310 end
311 end
312 end
313 end
314 end
315
316 local nofligatures = #ligatures
317
318 if nofligatures > 0 then
319 local characters = tfmdata.characters
320 local present = { }
321 local done = trace_baseinit and trace_ligatures and { }
322
323 for i=1,nofligatures do
324 local ligature = ligatures[i]
325 local unicode = ligature[1]
326 local tree = ligature[2]
327 make_1(present,tree,"ctx_"..unicode)
328 end
329
330 for i=1,nofligatures do
331 local ligature = ligatures[i]
332 local unicode = ligature[1]
333 local tree = ligature[2]
334 local lookupname = ligature[3]
335 make_2(present,tfmdata,characters,tree,"ctx_"..unicode,unicode,unicode,done,sequence)
336 end
337
338 end
339
340end
341
342local function preparepositionings(tfmdata,feature,value,validlookups,lookuplist)
343 local characters = tfmdata.characters
344 local descriptions = tfmdata.descriptions
345 local resources = tfmdata.resources
346 local properties = tfmdata.properties
347 local traceindeed = trace_baseinit and trace_kerns
348
349 for i=1,#lookuplist do
350 local sequence = lookuplist[i]
351 local steps = sequence.steps
352 local kind = sequence.type
353 local format = sequence.format
354 if kind == "gpos_pair" then
355 for i=1,#steps do
356 local step = steps[i]
357 local format = step.format
358 if format == "kern" or format == "move" then
359 for unicode, data in next, steps[i].coverage do
360 local character = characters[unicode]
361 local kerns = character.kerns
362 if not kerns then
363 kerns = { }
364 character.kerns = kerns
365 end
366 if traceindeed then
367 for otherunicode, kern in next, data do
368 if not kerns[otherunicode] and kern ~= 0 then
369 kerns[otherunicode] = kern
370 report_kern(feature,sequence,descriptions,unicode,otherunicode,kern)
371 end
372 end
373 else
374 for otherunicode, kern in next, data do
375 if not kerns[otherunicode] and kern ~= 0 then
376 kerns[otherunicode] = kern
377 end
378 end
379 end
380 end
381 else
382 for unicode, data in next, steps[i].coverage do
383 local character = characters[unicode]
384 local kerns = character.kerns
385 for otherunicode, kern in next, data do
386
387 local other = kern[2]
388 if other == true or (not other and not (kerns and kerns[otherunicode])) then
389 local kern = kern[1]
390 if kern == true then
391
392 elseif kern[1] ~= 0 or kern[2] ~= 0 or kern[4] ~= 0 then
393
394 else
395 kern = kern[3]
396 if kern ~= 0 then
397 if kerns then
398 kerns[otherunicode] = kern
399 else
400 kerns = { [otherunicode] = kern }
401 character.kerns = kerns
402 end
403 if traceindeed then
404 report_kern(feature,sequence,descriptions,unicode,otherunicode,kern)
405 end
406 end
407 end
408 end
409 end
410 end
411 end
412 end
413 end
414 end
415
416end
417
418local function initializehashes(tfmdata)
419
420end
421
422local function relocatessty(sequences,fullname)
423 local position = false
424 local sstydata = false
425 local nofsequences = #sequences
426 for s=1,#sequences do
427 local sequence = sequences[s]
428 local features = sequence.features
429 if features and features.ssty then
430 position = s
431 sstydata = sequence
432 end
433 end
434 if position and position ~= nofsequences then
435 table.remove(sequences,position)
436 sequences[nofsequences-1] = sstydata
437 if trace_preparing then
438 report_prepare("ssty feature relocated to the end in %a",fullname)
439 end
440 end
441end
442
443local function featuresinitializer(tfmdata,value)
444 if true then
445 local starttime = trace_preparing and os.clock()
446 local features = tfmdata.shared.features
447 local fullname = tfmdata.properties.fullname
448 if features and fullname then
449 initializehashes(tfmdata)
450 local collectlookups = otf.collectlookups
451 local rawdata = tfmdata.shared.rawdata
452 local properties = tfmdata.properties
453 local script = properties.script
454 local language = properties.language
455 local rawresources = rawdata.resources
456 local rawfeatures = rawresources and rawresources.features
457 local basesubstitutions = rawfeatures and rawfeatures.gsub
458 local basepositionings = rawfeatures and rawfeatures.gpos
459 local substitutionsdone = false
460 local positioningsdone = false
461
462 if basesubstitutions or basepositionings then
463 local sequences = tfmdata.resources.sequences
464 if basesubstitutions.ssty then
465
466 end
467 for s=1,#sequences do
468 local sequence = sequences[s]
469 local sfeatures = sequence.features
470 if sfeatures then
471 local order = sequence.order
472 if order then
473 for i=1,#order do
474 local feature = order[i]
475 local value = features[feature]
476 if value then
477 local script = script
478 local language = language
479if type(value) == "string" then
480 local t = utilities.parsers.settings_to_hash(value)
481 script = t and t.script or script
482 language = t and t.language or language
483end
484 local validlookups, lookuplist = collectlookups(rawdata,feature,script,language)
485 if not validlookups then
486
487 elseif basesubstitutions and basesubstitutions[feature] then
488 if trace_preparing then
489 report_prepare("filtering base %s feature %a for %a with value %a","sub",feature,fullname,value)
490 end
491 preparesubstitutions(tfmdata,feature,value,validlookups,lookuplist)
492 registerbasefeature(feature,value)
493 substitutionsdone = true
494 elseif basepositionings and basepositionings[feature] then
495 if trace_preparing then
496 report_prepare("filtering base %a feature %a for %a with value %a","pos",feature,fullname,value)
497 end
498 preparepositionings(tfmdata,feature,value,validlookups,lookuplist)
499 registerbasefeature(feature,value)
500 positioningsdone = true
501 end
502 end
503 end
504 end
505 end
506 end
507 end
508 registerbasehash(tfmdata)
509 end
510 if trace_preparing then
511 report_prepare("preparation time is %0.3f seconds for %a",os.clock()-starttime,fullname)
512 end
513 end
514end
515
516registerotffeature {
517 name = "features",
518 description = "features",
519 default = true,
520 initializers = {
521
522 base = featuresinitializer,
523 }
524}
525
526otf.basemodeinitializer = featuresinitializer
527 |