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