1if not modules then modules = { } end modules ['font-otc'] = {
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 insert, sortedkeys, sortedhash, tohash = table.insert, table.sortedkeys, table.sortedhash, table.tohash
10local type, next, tonumber = type, next, tonumber
11local lpegmatch = lpeg.match
12local utfbyte, utflen = utf.byte, utf.len
13local sortedhash = table.sortedhash
14
15
16
17local trace_loading = false trackers.register("otf.loading", function(v) trace_loading = v end)
18local report_otf = logs.reporter("fonts","otf loading")
19
20local fonts = fonts
21local otf = fonts.handlers.otf
22local registerotffeature = otf.features.register
23local setmetatableindex = table.setmetatableindex
24
25local fonthelpers = fonts.helpers
26local checkmerge = fonthelpers.checkmerge
27local checkflags = fonthelpers.checkflags
28local checksteps = fonthelpers.checksteps
29
30local normalized = {
31 substitution = "substitution",
32 single = "substitution",
33 ligature = "ligature",
34 alternate = "alternate",
35 multiple = "multiple",
36 kern = "kern",
37 pair = "pair",
38 single = "single",
39 chainsubstitution = "chainsubstitution",
40 chainposition = "chainposition",
41}
42
43local types = {
44 substitution = "gsub_single",
45 ligature = "gsub_ligature",
46 alternate = "gsub_alternate",
47 multiple = "gsub_multiple",
48 kern = "gpos_pair",
49 pair = "gpos_pair",
50 single = "gpos_single",
51 chainsubstitution = "gsub_contextchain",
52 chainposition = "gpos_contextchain",
53}
54
55local names = {
56 gsub_single = "gsub",
57 gsub_multiple = "gsub",
58 gsub_alternate = "gsub",
59 gsub_ligature = "gsub",
60 gsub_context = "gsub",
61 gsub_contextchain = "gsub",
62 gsub_reversecontextchain = "gsub",
63 gpos_single = "gpos",
64 gpos_pair = "gpos",
65 gpos_cursive = "gpos",
66 gpos_mark2base = "gpos",
67 gpos_mark2ligature = "gpos",
68 gpos_mark2mark = "gpos",
69 gpos_context = "gpos",
70 gpos_contextchain = "gpos",
71}
72
73setmetatableindex(types, function(t,k) t[k] = k return k end)
74
75local everywhere = { ["*"] = { ["*"] = true } }
76local noflags = { false, false, false, false }
77
78
79
80local function getrange(sequences,category)
81 local count = #sequences
82 local first = nil
83 local last = nil
84 for i=1,count do
85 local t = sequences[i].type
86 if t and names[t] == category then
87 if not first then
88 first = i
89 end
90 last = i
91 end
92 end
93 return first or 1, last or count
94end
95
96local function validspecification(specification,name)
97 local dataset = specification.dataset
98 if dataset then
99
100 elseif specification[1] then
101 dataset = specification
102 specification = { dataset = dataset }
103 else
104 dataset = { { data = specification.data } }
105 specification.data = nil
106 specification.coverage = dataset
107 specification.dataset = dataset
108 end
109 local first = dataset[1]
110 if first then
111 first = first.data
112 end
113 if not first then
114 report_otf("invalid feature specification, no dataset")
115 return
116 end
117 if type(name) ~= "string" then
118 name = specification.name or first.name
119 end
120 if type(name) ~= "string" then
121 report_otf("invalid feature specification, no name")
122 return
123 end
124 local n = #dataset
125 if n > 0 then
126 for i=1,n do
127 setmetatableindex(dataset[i],specification)
128 end
129 return specification, name
130 end
131end
132
133local function addfeature(data,feature,specifications)
134
135
136
137
138 if not specifications then
139 report_otf("missing specification")
140 return
141 end
142
143 local descriptions = data.descriptions
144 local resources = data.resources
145 local features = resources.features
146 local sequences = resources.sequences
147
148 if not features or not sequences then
149 report_otf("missing specification")
150 return
151 end
152
153 local alreadydone = resources.alreadydone
154 if not alreadydone then
155 alreadydone = { }
156 resources.alreadydone = alreadydone
157 end
158 if alreadydone[specifications] then
159 return
160 else
161 alreadydone[specifications] = true
162 end
163
164
165
166 local fontfeatures = resources.features or everywhere
167 local unicodes = resources.unicodes
168 local splitter = lpeg.splitter(" ",unicodes)
169 local done = 0
170 local skip = 0
171 local aglunicodes = false
172 local privateslot = fonthelpers.privateslot
173
174 local specifications = validspecification(specifications,feature)
175 if not specifications then
176
177 return
178 end
179
180 local p = lpeg.P("P")
181 * (lpeg.patterns.hexdigit^1/function(s) return tonumber(s,16) end)
182 * lpeg.P(-1)
183
184 local function tounicode(code)
185 if not code then
186 return
187 end
188 if type(code) == "number" then
189 return code
190 end
191 local u = unicodes[code]
192 if u then
193
194 return u
195 end
196 if utflen(code) == 1 then
197 u = utfbyte(code)
198 if u then
199 return u
200 end
201 end
202 if privateslot then
203 u = privateslot(code)
204 if u then
205
206 return u
207 end
208 end
209 local u = lpegmatch(p,code)
210 if u then
211
212 return u
213 end
214 if not aglunicodes then
215 aglunicodes = fonts.encodings.agl.unicodes
216 end
217 local u = aglunicodes[code]
218 if u then
219
220 return u
221 end
222 end
223
224 local coverup = otf.coverup
225 local coveractions = coverup.actions
226 local stepkey = coverup.stepkey
227 local register = coverup.register
228
229 local function prepare_substitution(list,featuretype,nocheck)
230 local coverage = { }
231 local cover = coveractions[featuretype]
232 for code, replacement in next, list do
233 local unicode = tounicode(code)
234 local description = descriptions[unicode]
235 if not nocheck and not description then
236
237 skip = skip + 1
238 else
239 if type(replacement) == "table" then
240 replacement = replacement[1]
241 end
242 replacement = tounicode(replacement)
243 if replacement and (nocheck or descriptions[replacement]) then
244 cover(coverage,unicode,replacement)
245 done = done + 1
246 else
247 skip = skip + 1
248 end
249 end
250 end
251 return coverage
252 end
253
254 local function prepare_alternate(list,featuretype,nocheck)
255 local coverage = { }
256 local cover = coveractions[featuretype]
257 for code, replacement in next, list do
258 local unicode = tounicode(code)
259 local description = descriptions[unicode]
260 if not nocheck and not description then
261 skip = skip + 1
262 elseif type(replacement) == "table" then
263 local r = { }
264 for i=1,#replacement do
265 local u = tounicode(replacement[i])
266 r[i] = (nocheck or descriptions[u]) and u or unicode
267 end
268 cover(coverage,unicode,r)
269 done = done + 1
270 else
271 local u = tounicode(replacement)
272 if u then
273 cover(coverage,unicode,{ u })
274 done = done + 1
275 else
276 skip = skip + 1
277 end
278 end
279 end
280 return coverage
281 end
282
283 local function prepare_multiple(list,featuretype,nocheck)
284 local coverage = { }
285 local cover = coveractions[featuretype]
286 for code, replacement in next, list do
287 local unicode = tounicode(code)
288 local description = descriptions[unicode]
289 if not nocheck and not description then
290 skip = skip + 1
291 elseif type(replacement) == "table" then
292 local r = { }
293 local n = 0
294 for i=1,#replacement do
295 local u = tounicode(replacement[i])
296 if nocheck or descriptions[u] then
297 n = n + 1
298 r[n] = u
299 end
300 end
301 if n > 0 then
302 cover(coverage,unicode,r)
303 done = done + 1
304 else
305 skip = skip + 1
306 end
307 else
308 local u = tounicode(replacement)
309 if u then
310 cover(coverage,unicode,{ u })
311 done = done + 1
312 else
313 skip = skip + 1
314 end
315 end
316 end
317 return coverage
318 end
319
320 local function prepare_ligature(list,featuretype,nocheck)
321 local coverage = { }
322 local cover = coveractions[featuretype]
323 for code, ligature in next, list do
324 local unicode = tounicode(code)
325 local description = descriptions[unicode]
326 if not nocheck and not description then
327 skip = skip + 1
328 else
329 if type(ligature) == "string" then
330 ligature = { lpegmatch(splitter,ligature) }
331 end
332 local present = true
333 for i=1,#ligature do
334 local l = ligature[i]
335 local u = tounicode(l)
336 if nocheck or descriptions[u] then
337 ligature[i] = u
338 else
339 present = false
340 break
341 end
342 end
343 if present then
344 cover(coverage,unicode,ligature)
345 done = done + 1
346 else
347 skip = skip + 1
348 end
349 end
350 end
351 return coverage
352 end
353
354 local function resetspacekerns()
355
356
357
358 data.properties.hasspacekerns = true
359 data.resources .spacekerns = nil
360 end
361
362 local function prepare_kern(list,featuretype)
363 local coverage = { }
364 local cover = coveractions[featuretype]
365 local isspace = false
366 for code, replacement in next, list do
367 local unicode = tounicode(code)
368 local description = descriptions[unicode]
369 if description and type(replacement) == "table" then
370 local r = { }
371 for k, v in next, replacement do
372 local u = tounicode(k)
373 if u then
374 r[u] = v
375 if u == 32 then
376 isspace = true
377 end
378 end
379 end
380 if next(r) then
381 cover(coverage,unicode,r)
382 done = done + 1
383 if unicode == 32 then
384 isspace = true
385 end
386 else
387 skip = skip + 1
388 end
389 else
390 skip = skip + 1
391 end
392 end
393 if isspace then
394 resetspacekerns()
395 end
396 return coverage
397 end
398
399 local function prepare_pair(list,featuretype)
400 local coverage = { }
401 local cover = coveractions[featuretype]
402 if cover then
403 for code, replacement in next, list do
404 local unicode = tounicode(code)
405 local description = descriptions[unicode]
406 if description and type(replacement) == "table" then
407 local r = { }
408 for k, v in next, replacement do
409 local u = tounicode(k)
410 if u then
411 r[u] = v
412 if u == 32 then
413 isspace = true
414 end
415 end
416 end
417 if next(r) then
418 cover(coverage,unicode,r)
419 done = done + 1
420 if unicode == 32 then
421 isspace = true
422 end
423 else
424 skip = skip + 1
425 end
426 else
427 skip = skip + 1
428 end
429 end
430 if isspace then
431 resetspacekerns()
432 end
433 else
434 report_otf("unknown cover type %a",featuretype)
435 end
436 return coverage
437 end
438
439 local prepare_single = prepare_pair
440
441 local function hassteps(lookups)
442 if lookups then
443 for i=1,#lookups do
444 local l = lookups[i]
445 if l then
446 for j=1,#l do
447 local l = l[j]
448 if l then
449 local n = l.nofsteps
450 if not n then
451
452 return true
453 elseif n > 0 then
454 return true
455 end
456 end
457 end
458 end
459 end
460 end
461 return false
462 end
463
464 local function prepare_chain(list,featuretype,sublookups,nocheck)
465
466 local rules = list.rules
467 local coverage = { }
468 if rules then
469 local rulehash = { }
470 local rulesize = 0
471 local lookuptype = types[featuretype]
472 for nofrules=1,#rules do
473 local rule = rules[nofrules]
474 local current = rule.current
475 local before = rule.before
476 local after = rule.after
477 local replacements = rule.replacements or false
478 local sequence = { }
479 local nofsequences = 0
480 if before then
481 for n=1,#before do
482 nofsequences = nofsequences + 1
483 sequence[nofsequences] = before[n]
484 end
485 end
486 local start = nofsequences + 1
487 for n=1,#current do
488 nofsequences = nofsequences + 1
489 sequence[nofsequences] = current[n]
490 end
491 local stop = nofsequences
492 if after then
493 for n=1,#after do
494 nofsequences = nofsequences + 1
495 sequence[nofsequences] = after[n]
496 end
497 end
498 local lookups = rule.lookups or false
499 local subtype = nil
500 if lookups and sublookups then
501 local l = { }
502 for k, v in sortedhash(lookups) do
503 local t = type(v)
504 if t == "table" then
505
506 for i=1,#v do
507 local vi = v[i]
508 if type(vi) ~= "table" then
509 v[i] = { vi }
510 end
511 end
512 l[k] = v
513 elseif t == "number" then
514 local lookup = sublookups[v]
515 if lookup then
516 l[k] = { lookup }
517 if not subtype then
518 subtype = lookup.type
519 end
520 elseif v == 0 then
521 l[k] = { { type = "gsub_remove", nosteps = true } }
522 else
523 l[k] = false
524 end
525 else
526 l[k] = false
527 end
528 end
529 if nocheck then
530
531 rule.lookups = l
532 end
533 lookups = l
534 end
535 if nofsequences > 0 then
536
537 if hassteps(lookups) then
538 local hashed = { }
539 for i=1,nofsequences do
540 local t = { }
541 local s = sequence[i]
542 for i=1,#s do
543 local u = tounicode(s[i])
544 if u then
545 t[u] = true
546 end
547 end
548 hashed[i] = t
549 end
550 sequence = hashed
551 rulesize = rulesize + 1
552 rulehash[rulesize] = {
553 nofrules,
554 lookuptype,
555 sequence,
556 start,
557 stop,
558 lookups,
559 replacements,
560 subtype,
561 }
562 for unic in sortedhash(sequence[start]) do
563 local cu = coverage[unic]
564 if not cu then
565 coverage[unic] = rulehash
566 end
567 end
568 sequence.n = nofsequences
569 else
570
571 end
572 end
573 end
574 rulehash.n = rulesize
575 end
576 return coverage
577 end
578
579 local dataset = specifications.dataset
580
581 local function report(name,category,position,first,last,sequences)
582 report_otf("injecting name %a of category %a at position %i in [%i,%i] of [%i,%i]",
583 name,category,position,first,last,1,#sequences)
584 end
585
586 local function inject(specification,sequences,sequence,first,last,category,name)
587 local position = specification.position or false
588 if not position then
589 position = specification.prepend
590 if position == true then
591 if trace_loading then
592 report(name,category,first,first,last,sequences)
593 end
594 insert(sequences,first,sequence)
595 return
596 end
597 end
598 if not position then
599 position = specification.append
600 if position == true then
601 if trace_loading then
602 report(name,category,last+1,first,last,sequences)
603 end
604 insert(sequences,last+1,sequence)
605 return
606 end
607 end
608 local kind = type(position)
609 if kind == "string" then
610 local index = false
611 for i=first,last do
612 local s = sequences[i]
613 local f = s.features
614 if f then
615 for k in sortedhash(f) do
616 if k == position then
617 index = i
618 break
619 end
620 end
621 if index then
622 break
623 end
624 end
625 end
626 if index then
627 position = index
628 else
629 position = last + 1
630 end
631 elseif kind == "number" then
632 if position < 0 then
633 position = last - position + 1
634 end
635 if position > last then
636 position = last + 1
637 elseif position < first then
638 position = first
639 end
640 else
641 position = last + 1
642 end
643 if trace_loading then
644 report(name,category,position,first,last,sequences)
645 end
646 insert(sequences,position,sequence)
647 end
648
649 for s=1,#dataset do
650 local specification = dataset[s]
651 local valid = specification.valid
652 local feature = specification.name or feature
653 if not feature or feature == "" then
654 report_otf("no valid name given for extra feature")
655 elseif not valid or valid(data,specification,feature) then
656 local initialize = specification.initialize
657 if initialize then
658
659 specification.initialize = initialize(specification,data) and initialize or nil
660 end
661 local askedfeatures = specification.features or everywhere
662 local askedsteps = specification.steps or specification.subtables or { specification.data } or { }
663 local featuretype = specification.type or "substitution"
664 local featureaction = false
665 local featureflags = specification.flags or noflags
666 local nocheck = specification.nocheck
667 local mapping = specification.mapping
668 local featureorder = specification.order or { feature }
669 local featurechain = (featuretype == "chainsubstitution" or featuretype == "chainposition") and 1 or 0
670 local nofsteps = 0
671 local steps = { }
672 local sublookups = specification.lookups
673 local category = nil
674 local steptype = nil
675 local sequence = nil
676
677 if fonts.handlers.otf.handlers[featuretype] then
678 featureaction = true
679 else
680 featuretype = normalized[specification.type or "substitution"] or "substitution"
681 end
682
683 checkflags(specification,resources)
684
685 for k, v in next, askedfeatures do
686 if v[1] then
687 askedfeatures[k] = tohash(v)
688 end
689 end
690
691 if featureflags[1] then featureflags[1] = "mark" end
692 if featureflags[2] then featureflags[2] = "ligature" end
693 if featureflags[3] then featureflags[3] = "base" end
694
695 if featureaction then
696
697 category = "gsub"
698 sequence = {
699 features = { [feature] = askedfeatures },
700 flags = featureflags,
701 name = feature,
702 order = featureorder,
703 type = featuretype,
704
705 nofsteps = 0,
706 }
707
708 else
709
710 if sublookups then
711 local s = { }
712 for i=1,#sublookups do
713 local specification = sublookups[i]
714 local askedsteps = specification.steps or specification.subtables or { specification.data } or { }
715 local featuretype = normalized[specification.type or "substitution"] or "substitution"
716 local featureflags = specification.flags or noflags
717 local nofsteps = 0
718 local steps = { }
719 for i=1,#askedsteps do
720 local list = askedsteps[i]
721 local coverage = nil
722 local format = nil
723 if featuretype == "substitution" then
724 coverage = prepare_substitution(list,featuretype,nocheck)
725 elseif featuretype == "ligature" then
726 coverage = prepare_ligature(list,featuretype,nocheck)
727 elseif featuretype == "alternate" then
728 coverage = prepare_alternate(list,featuretype,nocheck)
729 elseif featuretype == "multiple" then
730 coverage = prepare_multiple(list,featuretype,nocheck)
731 elseif featuretype == "kern" or featuretype == "move" then
732 format = featuretype
733 coverage = prepare_kern(list,featuretype)
734 elseif featuretype == "pair" then
735 format = "pair"
736 coverage = prepare_pair(list,featuretype)
737 elseif featuretype == "single" then
738 format = "single"
739 coverage = prepare_single(list,featuretype)
740 end
741 if coverage and next(coverage) then
742 nofsteps = nofsteps + 1
743 steps[nofsteps] = register(coverage,featuretype,format,feature,nofsteps,descriptions,resources)
744 end
745 end
746
747 checkmerge(specification)
748 checksteps(specification)
749
750 s[i] = {
751 [stepkey] = steps,
752 nofsteps = nofsteps,
753 flags = featureflags,
754 type = types[featuretype],
755 }
756 end
757 sublookups = s
758 end
759
760 for i=1,#askedsteps do
761 local list = askedsteps[i]
762 local coverage = nil
763 local format = nil
764 if featuretype == "substitution" then
765
766 category = "gsub"
767 coverage = (mapping and list) or prepare_substitution(list,featuretype,nocheck)
768 elseif featuretype == "ligature" then
769 category = "gsub"
770 coverage = prepare_ligature(list,featuretype,nocheck)
771 elseif featuretype == "alternate" then
772 category = "gsub"
773 coverage = prepare_alternate(list,featuretype,nocheck)
774 elseif featuretype == "multiple" then
775 category = "gsub"
776 coverage = prepare_multiple(list,featuretype,nocheck)
777 elseif featuretype == "kern" or featuretype == "move" then
778 category = "gpos"
779 format = featuretype
780 coverage = prepare_kern(list,featuretype)
781 elseif featuretype == "pair" then
782 category = "gpos"
783 format = "pair"
784 coverage = prepare_pair(list,featuretype)
785 elseif featuretype == "single" then
786 category = "gpos"
787 format = "single"
788 coverage = prepare_single(list,featuretype)
789 elseif featuretype == "chainsubstitution" then
790 category = "gsub"
791 coverage = prepare_chain(list,featuretype,sublookups,nocheck)
792 elseif featuretype == "chainposition" then
793 category = "gpos"
794 coverage = prepare_chain(list,featuretype,sublookups,nocheck)
795 else
796 report_otf("not registering feature %a, unknown category",feature)
797 return
798 end
799 if coverage and next(coverage) then
800 nofsteps = nofsteps + 1
801 steps[nofsteps] = register(coverage,featuretype,format,feature,nofsteps,descriptions,resources)
802 end
803 end
804
805 if nofsteps > 0 then
806 sequence = {
807 chain = featurechain,
808 features = { [feature] = askedfeatures },
809 flags = featureflags,
810 name = feature,
811 order = featureorder,
812 [stepkey] = steps,
813 nofsteps = nofsteps,
814 type = types[featuretype],
815 }
816 end
817 end
818
819 if sequence then
820
821 checkflags(sequence,resources)
822 checkmerge(sequence)
823 checksteps(sequence)
824
825 local first, last = getrange(sequences,category)
826 inject(specification,sequences,sequence,first,last,category,feature)
827
828 local features = fontfeatures[category]
829 if not features then
830 features = { }
831 fontfeatures[category] = features
832 end
833 local k = features[feature]
834 if not k then
835 k = { }
836 features[feature] = k
837 end
838
839 for script, languages in next, askedfeatures do
840 local kk = k[script]
841 if not kk then
842 kk = { }
843 k[script] = kk
844 end
845 for language, value in next, languages do
846 kk[language] = value
847 end
848 end
849 end
850
851 end
852 end
853 if trace_loading then
854 report_otf("registering feature %a, affected glyphs %a, skipped glyphs %a",feature,done,skip)
855 end
856
857end
858
859otf.enhancers.addfeature = addfeature
860
861local extrafeatures = { }
862local knownfeatures = { }
863
864function otf.addfeature(name,specification)
865 if type(name) == "table" then
866 specification = name
867 end
868 if type(specification) ~= "table" then
869 report_otf("invalid feature specification, no valid table")
870 return
871 end
872 specification, name = validspecification(specification,name)
873 if name and specification then
874 local slot = knownfeatures[name]
875 if not slot then
876
877 slot = #extrafeatures + 1
878 knownfeatures[name] = slot
879 elseif specification.overload == false then
880
881 slot = #extrafeatures + 1
882 knownfeatures[name] = slot
883 else
884
885 end
886 specification.name = name
887 extrafeatures[slot] = specification
888
889 end
890end
891
892
893
894
895
896local function enhance(data,filename,raw)
897 for slot=1,#extrafeatures do
898 local specification = extrafeatures[slot]
899 addfeature(data,specification.name,specification)
900 end
901end
902
903otf.enhancers.enhance = enhance
904
905otf.enhancers.register("check extra features",enhance)
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951 |