font-otc.lua /size: 33 Kb    last modification: 2021-10-28 13:50
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-- we assume that the other otf stuff is loaded already
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) -- "key"
74
75local everywhere = { ["*"] = { ["*"] = true } } -- or: { ["*"] = { "*" } }
76local noflags    = { false, false, false, false }
77
78-- beware: shared, maybe we should copy the sequence
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        -- okay
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    -- todo: add some validator / check code so that we're more tolerant to
136    -- user errors
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    -- feature has to be unique but the name entry wins eventually
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     -- report_otf("invalid specification")
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         -- unicodes[code] = u
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) -- no creation !
204            if u then
205             -- unicodes[code] = u
206                return u
207            end
208        end
209        local u = lpegmatch(p,code)
210        if u then
211         -- unicodes[code] = u
212            return u
213        end
214        if not aglunicodes then
215            aglunicodes = fonts.encodings.agl.unicodes -- delayed
216        end
217        local u = aglunicodes[code]
218        if u then
219         -- unicodes[code] = u
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                -- todo: trace !
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        -- a bit of a hack, this nil setting but it forces a
356        -- rehash of the resources needed .. the feature itself
357        -- should be a kern (at least for now)
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 -- we could have a better test on the spec
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                                -- gsub_remove
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        -- todo: coveractions
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                            -- already ok
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 -- { false } -- new
524                            end
525                        else
526                            l[k] = false -- { false } -- new
527                        end
528                    end
529                    if nocheck then
530                        -- fragile
531                        rule.lookups = l --no, because checking can spoil it
532                    end
533                    lookups = l
534                end
535                if nofsequences > 0 then -- we merge coverage into one
536                    -- we copy as we can have different fonts
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,     -- 1
554                            lookuptype,   -- 2
555                            sequence,     -- 3
556                            start,        -- 4
557                            stop,         -- 5
558                            lookups,      -- 6 (6/7 also signal of what to do)
559                            replacements, -- 7
560                            subtype,      -- 8
561                        }
562                        for unic in sortedhash(sequence[start]) do
563                            local cu = coverage[unic]
564                            if not cu then
565                                coverage[unic] = rulehash -- can now be done cleaner i think
566                            end
567                        end
568                        sequence.n = nofsequences
569                    else
570                     -- report_otf("no steps for %a",lookuptype) -- e.g. in primes feature
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 -- next, 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 -- nowhere used
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 -- anum uses this
656            local initialize = specification.initialize
657            if initialize then
658                -- when false is returned we initialize only once
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 -- function based
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, -- redundant
702                    order     = featureorder,
703                    type      = featuretype,
704                 -- steps     = { },
705                    nofsteps  = 0, -- just in case we test for that
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                        -- see font-imp-tweaks: we directly pass a mapping so no checks done
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, -- redundant
811                        order     = featureorder,
812                        [stepkey] = steps,
813                        nofsteps  = nofsteps,
814                        type      = types[featuretype],
815                    }
816                end
817            end
818
819            if sequence then
820                -- script = { lang1, lang2, lang3 } or script = { lang1 = true, ... }
821                checkflags(sequence,resources)
822                checkmerge(sequence)
823                checksteps(sequence)
824                -- position | prepend | append
825                local first, last = getrange(sequences,category)
826                inject(specification,sequences,sequence,first,last,category,feature)
827                -- register in metadata (merge as there can be a few)
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            -- we have a new one
877            slot = #extrafeatures + 1
878            knownfeatures[name] = slot
879        elseif specification.overload == false then
880            -- we add an extre one
881            slot = #extrafeatures + 1
882            knownfeatures[name] = slot
883        else
884            -- we overload a previous one
885        end
886        specification.name  = name -- to be sure
887        extrafeatures[slot] = specification
888     -- report_otf("adding feature %a @ %i",name,slot)
889    end
890end
891
892-- for feature, specification in next, extrafeatures do
893--     addfeature(data,feature,specification)
894-- end
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-- fonts.handlers.otf.features.register {
908--     name        = 'hangulfix',
909--     description = 'fixes for hangul',
910-- }
911
912-- fonts.handlers.otf.addfeature {
913--     name = "stest",
914--     type = "substitution",
915--     data = {
916--         a = "X",
917--         b = "P",
918--     }
919-- }
920-- fonts.handlers.otf.addfeature {
921--     name = "atest",
922--     type = "alternate",
923--     data = {
924--         a = { "X", "Y" },
925--         b = { "P", "Q" },
926--     }
927-- }
928-- fonts.handlers.otf.addfeature {
929--     name = "mtest",
930--     type = "multiple",
931--     data = {
932--         a = { "X", "Y" },
933--         b = { "P", "Q" },
934--     }
935-- }
936-- fonts.handlers.otf.addfeature {
937--     name = "ltest",
938--     type = "ligature",
939--     data = {
940--         X = { "a", "b" },
941--         Y = { "d", "a" },
942--     }
943-- }
944-- fonts.handlers.otf.addfeature {
945--     name = "ktest",
946--     type = "kern",
947--     data = {
948--         a = { b = -500 },
949--     }
950-- }
951