s-fonts-tables.lua /size: 29 Kb    last modification: 2023-12-21 09:45
1if not modules then modules = { } end modules ['s-fonts-tables'] = {
2    version   = 1.001,
3    comment   = "companion to s-fonts-tables.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-- Thsi file needs to be updated to LMTX!
10
11moduledata.fonts          = moduledata.fonts        or { }
12moduledata.fonts.tables   = moduledata.fonts.tables or { }
13
14require("font-cft")
15
16local rawget, type = rawget, type
17
18local setmetatableindex   = table.setmetatableindex
19local sortedhash          = table.sortedhash
20local sortedkeys          = table.sortedkeys
21local concat              = table.concat
22local insert              = table.insert
23local remove              = table.remove
24local formatters          = string.formatters
25
26local tabletracers        = moduledata.fonts.tables
27
28local new_glyph           = nodes.pool.glyph
29local copy_node           = nodes.copy
30local setlink             = nodes.setlink
31local hpack               = nodes.hpack
32local applyvisuals        = nodes.applyvisuals
33
34local lefttoright_code    = (tex.directioncodes and tex.directioncodes.lefttoright) or nodes.dirvalues.lefttoright -- LMTX
35
36local handle_positions    = fonts.handlers.otf.datasetpositionprocessor
37local handle_injections   = nodes.injections.handler
38
39local context             = context
40local ctx_sequence        = context.formatted.sequence
41local ctx_char            = context.char
42local ctx_setfontid       = context.setfontid
43local ctx_type            = context.formatted.type
44local ctx_dontleavehmode  = context.dontleavehmode
45local ctx_startPair       = context.startPair
46local ctx_stopPair        = context.stopPair
47local ctx_startSingle     = context.startSingle
48local ctx_stopSingle      = context.stopSingle
49local ctx_startSingleKern = context.startSingleKern
50local ctx_stopSingleKern  = context.stopSingleKern
51local ctx_startPairKern   = context.startPairKern
52local ctx_stopPairKern    = context.stopPairKern
53
54local ctx_NC = context.NC
55local ctx_NR = context.NR
56
57local digits = {
58    dflt = {
59        dflt = "1234567890 1/2",
60    },
61    arab = {
62        dflt = "",
63    },
64    latn = {
65        dflt = "1234567890 1/2",
66    }
67}
68
69local punctuation = {
70    dflt = {
71        dflt = ". , : ; ? ! ‹ › « »",
72    },
73}
74
75local symbols = {
76    dflt = {
77        dflt = "@ # $ % & * () [] {} <> + - = / |",
78    },
79}
80
81local LATN = "abcdefghijklmnopqrstuvwxyz"
82
83local uppercase = {
84    latn = {
85        dflt = LATN,
86        fra  = LATN .. " ÀÁÂÈÉÊÒÓÔÙÚÛÆÇ",
87    },
88    grek = {
89        dftl = "ΑΒΓΔΕΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ",
90    },
91    cyrl= {
92        dflt = "АБВГДЕЖЗИІЙКЛМНОПРСТУФХЦЧШЩЪЫЬѢЭЮЯѲ"
93    },
94}
95
96local latn = "abcdefghijklmnopqrstuvwxyz"
97
98local lowercase = {
99    latn = {
100        dftl = latn,
101        nld  = latn .. " ïèéë",
102        deu  = latn .. " äöüß",
103        fra  = latn .. " àáâèéêòóôùúûæç",
104    },
105    grek = {
106        dftl = "αβγδεηθικλμνξοπρστυφχψω",
107    },
108    cyrl= {
109        dflt = "абвгдежзиійклмнопрстуфхцчшщъыьѣэюяѳ"
110    },
111    arab = {
112        dflt = "ابجدهوزحطيكلمنسعفصقرشتثخذضظغ"
113    },
114}
115
116local samples = {
117    digits      = digits,
118    punctuation = punctuation,
119    symbols     = symbols,
120    uppercase   = uppercase,
121    lowercase   = lowercase,
122}
123
124tabletracers.samples = samples
125
126setmetatableindex(uppercase,        function(t,k) return rawget(t,"latn") end)
127setmetatableindex(lowercase,        function(t,k) return rawget(t,"latn") end)
128setmetatableindex(digits,           function(t,k) return rawget(t,"dflt") end)
129setmetatableindex(symbols,          function(t,k) return rawget(t,"dflt") end)
130setmetatableindex(punctuation,      function(t,k) return rawget(t,"dflt") end)
131
132setmetatableindex(uppercase.latn,   function(t,k) return rawget(t,"dflt") end)
133setmetatableindex(uppercase.grek,   function(t,k) return rawget(t,"dflt") end)
134setmetatableindex(uppercase.cyrl,   function(t,k) return rawget(t,"dflt") end)
135
136setmetatableindex(lowercase.latn,   function(t,k) return rawget(t,"dflt") end)
137setmetatableindex(lowercase.grek,   function(t,k) return rawget(t,"dflt") end)
138setmetatableindex(lowercase.cyrl,   function(t,k) return rawget(t,"dflt") end)
139
140setmetatableindex(digits.dflt,      function(t,k) return rawget(t,"dflt") end)
141setmetatableindex(symbols.dflt,     function(t,k) return rawget(t,"dflt") end)
142setmetatableindex(punctuation.dflt, function(t,k) return rawget(t,"dflt") end)
143
144-- scaled boolean string scale string float cardinal
145
146local function checked(specification)
147    specification   = interfaces.checkedspecification(specification)
148    local id, cs    = fonts.definers.internal(specification,"<module:fonts:features:font>")
149    local tfmdata   = fonts.hashes.identifiers[id]
150    local resources = tfmdata.resources
151    return tfmdata, id, resources
152end
153
154local function nothing()
155    context("no entries")
156    context.par()
157end
158
159local function typesettable(t,keys,synonyms,nesting,prefix,depth)
160    if t and next(keys) then
161        if not prefix then
162            context.starttabulate { "|Tl|Tl|Tl|" }
163        end
164        for k, v in sortedhash(keys) do
165            if k == "synonyms" then
166            elseif type(v) ~= "table" then
167                ctx_NC()
168                if prefix then
169                    context("%s.%s",prefix,k)
170                else
171                    context(k)
172                end
173                ctx_NC()
174             -- print(v)
175                local tk = t[k]
176                if v == "<boolean>" then
177                    context(tostring(tk or false))
178                elseif not tk then
179                    context("<unset>")
180                elseif k == "filename" then
181                    context(file.basename(tk))
182             -- elseif v == "basepoints" then
183             --     context("%sbp",tk)
184                elseif v == "<scaled>" then
185                    context("%p",tk)
186                elseif v == "<table>" then
187                    context("<table>")
188                else
189                    context(tostring(tk))
190                end
191                ctx_NC()
192                local synonym = (not prefix and synonyms[k]) or (prefix and synonyms[formatters["%s.%s"](prefix,k)])
193                if synonym then
194                    context("(% t)",synonym)
195                end
196                ctx_NC()
197                ctx_NR()
198            elseif nesting == false then
199                context("<table>")
200            elseif next(v) then
201                typesettable(t[k],v,synonyms,nesting,k,true)
202            end
203        end
204        if not prefix then
205            context.stoptabulate()
206        end
207        return
208    end
209    if not depth then
210        nothing()
211    end
212end
213
214local function typeset(t,keys,nesting,prefix)
215    local synonyms = keys.synonyms or { }
216    local collected = { }
217    for k, v in next, synonyms do
218        local c = collected[v]
219        if not c then
220            c = { }
221            collected[v] = c
222        end
223        c[#c+1] = k
224    end
225    for k, v in next, collected do
226        table.sort(v)
227    end
228    typesettable(t,keys,collected,nesting,prefix)
229end
230
231tabletracers.typeset = typeset
232
233-- function tabletracers.showproperties(nesting)
234--     local tfmdata = fonts.hashes.identifiers[true]
235--     typeset(tfmdata.properties,fonts.constructors.keys.properties,nesting)
236-- end
237
238-- function tabletracers.showparameters(nesting)
239--     local tfmdata = fonts.hashes.identifiers[true]
240--     typeset(tfmdata.parameters,fonts.constructors.keys.parameters,nesting)
241-- end
242
243function tabletracers.showproperties(specification)
244    local tfmdata = checked(specification)
245    if tfmdata then
246        typeset(tfmdata.properties,fonts.constructors.keys.properties)
247    else
248        nothing()
249    end
250end
251
252function tabletracers.showparameters(specification)
253    local tfmdata = checked(specification)
254    if tfmdata then
255        typeset(tfmdata.parameters,fonts.constructors.keys.parameters)
256    else
257        nothing()
258    end
259end
260
261local f_u = formatters["%U"]
262local f_p = formatters["%p"]
263
264local function morept(t)
265    local r = { }
266    for i=1,t do
267        r[i] = f_p(t[i])
268    end
269    return concat(r," ")
270end
271
272local function noprefix(kind)
273    kind = string.gsub(kind,"^gpos_","")
274    kind = string.gsub(kind,"^gsub_","")
275    return kind
276end
277
278local function banner(index,i,format,kind,order,chain)
279    if chain then
280        ctx_sequence("sequence: %i, step %i, format: %s, kind: %s, features: % t, chain: %s",
281            index,i,format,noprefix(kind),order,noprefix(chain))
282    else
283        ctx_sequence("sequence: %i, step %i, format: %s, kind: %s, features: % t",
284            index,i,format,noprefix(kind),order)
285    end
286end
287
288function tabletracers.showpositionings(specification)
289
290    local tfmdata, fontid, resources = checked(specification)
291
292    if resources then
293
294        local direction = lefttoright_code -- not that relevant probably
295        local sequences = resources.sequences
296        local marks     = resources.marks
297        local visuals   = "fontkern,glyph,box"
298
299        local datasets  = fonts.handlers.otf.dataset(tfmdata,fontid,0)
300
301        local function process(dataset,sequence,kind,order,chain)
302            local steps = sequence.steps
303            local order = sequence.order or order
304            local index = sequence.index
305            for i=1,#steps do
306                local step   = steps[i]
307                local format = step.format
308                banner(index,i,format,kind,order,chain)
309                if kind == "gpos_pair" then
310                    local format = step.format
311                    if "kern" or format == "move" then
312                        for first, seconds in sortedhash(step.coverage) do
313                            local done = false
314                            local zero = 0
315                            for second, kern in sortedhash(seconds) do
316                                if kern == 0 then
317                                    zero = zero + 1
318                                else
319                                    if not done then
320                                        ctx_startPairKern()
321                                    end
322                                    local one = new_glyph(fontid,first)
323                                    local two = new_glyph(fontid,second)
324                                    local raw = setlink(copy_node(one),copy_node(two))
325                                    local pos = setlink(done and one or copy_node(one),copy_node(two))
326                                    pos, okay = handle_positions(pos,fontid,direction,dataset)
327                                    pos = handle_injections(pos)
328                                    applyvisuals(raw,visuals)
329                                    applyvisuals(pos,visuals)
330                                    pos = hpack(pos,"exact",nil,direction)
331                                    raw = hpack(raw,"exact",nil,direction)
332                                    ctx_NC() if not done then context(f_u(first)) end
333                                    ctx_NC() if not done then ctx_dontleavehmode() context(one) end
334                                    ctx_NC() context(f_u(second))
335                                    ctx_NC() ctx_dontleavehmode() context(two)
336                                    ctx_NC() context("%p",kern)
337                                    ctx_NC() ctx_dontleavehmode() context(raw)
338                                    ctx_NC() ctx_dontleavehmode() context(pos)
339                                    ctx_NC() ctx_NR()
340                                    done = true
341                                end
342                            end
343                            if done then
344                                ctx_stopPairKern()
345                            end
346                            if zero > 0 then
347                                ctx_type("zero: %s",zero)
348                            end
349                        end
350                    elseif format == "pair" then
351                        for first, seconds in sortedhash(step.coverage) do
352                            local done     = false
353                            local allnull  = 0
354                            local allzero  = 0
355                            local zeronull = 0
356                            local nullzero = 0
357                            for second, pair in sortedhash(seconds) do
358                                local pfirst  = pair[1]
359                                local psecond = pair[2]
360                                if not pfirst and not psecond then
361                                    allnull = allnull + 1
362                                elseif pfirst == true and psecond == true then
363                                    allzero = allzero + 1
364                                elseif pfirst == true and not psecond then
365                                    zeronull = zeronull + 1
366                                elseif not pfirst and psecond == true then
367                                    nullzero = nullzero + 1
368                                else
369                                    if pfirst == true then
370                                        pfirst = "all zero"
371                                    elseif pfirst then
372                                        pfirst = morept(pfirst)
373                                    else
374                                        pfirst = "no first"
375                                    end
376                                    if psecond == true then
377                                        psecond = "all zero"
378                                    elseif psecond then
379                                        psecond = morept(psecond)
380                                    else
381                                        psecond = "no second"
382                                    end
383                                    if not done then
384                                        ctx_startPair()
385                                    end
386                                    local one = new_glyph(fontid,first)
387                                    local two = new_glyph(fontid,second)
388                                    local raw = setlink(copy_node(one),copy_node(two))
389                                    local pos = setlink(done and one or copy_node(one),copy_node(two))
390                                    pos, okay = handle_positions(pos,fontid,direction,dataset)
391                                    pos = handle_injections(pos)
392                                    applyvisuals(raw,visuals)
393                                    applyvisuals(pos,visuals)
394                                    pos = hpack(pos,"exact",nil,direction)
395                                    raw = hpack(raw,"exact",nil,direction)
396                                    ctx_NC() if not done then context(f_u(first)) end
397                                    ctx_NC() if not done then ctx_dontleavehmode() context(one) end
398                                    ctx_NC() context(f_u(second))
399                                    ctx_NC() ctx_dontleavehmode() context(two)
400                                    ctx_NC() context(pfirst)
401                                    ctx_NC() context(psecond)
402                                    ctx_NC() ctx_dontleavehmode() context(raw)
403                                    ctx_NC() ctx_dontleavehmode() context(pos)
404                                    ctx_NC() ctx_NR()
405                                    done = true
406                                end
407                            end
408                            if done then
409                                ctx_stopPair()
410                            end
411                            if allnull > 0 or allzero > 0 or zeronull > 0 or nullzero > 0 then
412                                ctx_type("both null: %s, both zero: %s, zero and null: %s, null and zero: %s",
413                                    allnull,allzero,zeronull,nullzero)
414                            end
415                        end
416                    else
417                        -- maybe
418                    end
419                elseif kind == "gpos_single" then
420                    local format = step.format
421                    if format == "kern" or format == "move" then
422                        local done = false
423                        local zero = 0
424                        for first, kern in sortedhash(step.coverage) do
425                            if kern == 0 then
426                                zero = zero + 1
427                            else
428                                if not done then
429                                    ctx_startSingleKern()
430                                end
431                                local one = new_glyph(fontid,first)
432                                local raw = copy_node(one)
433                                local pos = copy_node(one)
434                                pos, okay = handle_positions(pos,fontid,direction,dataset)
435                                pos = handle_injections(pos)
436                                applyvisuals(raw,visuals)
437                                applyvisuals(pos,visuals)
438                                pos = hpack(pos,"exact",nil,direction)
439                                raw = hpack(raw,"exact",nil,direction)
440                                ctx_NC() context(f_u(first))
441                                ctx_NC() ctx_dontleavehmode() context(one)
442                                ctx_NC() context("%p",kern)
443                                ctx_NC() ctx_dontleavehmode() context(raw)
444                                ctx_NC() ctx_dontleavehmode() context(pos)
445                                ctx_NC() ctx_NR()
446                                done = true
447                            end
448                        end
449                        if done then
450                            ctx_stopSingleKern()
451                        end
452                        if zero > 0 then
453                            ctx_type("zero: %i",zero)
454                        end
455                    elseif format == "single" then
456                        local done = false
457                        local zero = 0
458                        local null = 0
459                        for first, single in sortedhash(step.coverage) do
460                            if single == false then
461                                null = null + 1
462                            elseif single == true then
463                                zero = zero + 1
464                            else
465                                single = morept(single)
466                                if not done then
467                                    ctx_startSingle()
468                                end
469                                local one = new_glyph(fontid,first)
470                                local raw = copy_node(one)
471                                local pos = copy_node(one)
472                                pos, okay = handle_positions(pos,fontid,direction,dataset)
473                                pos = handle_injections(pos)
474                                applyvisuals(raw,visuals)
475                                applyvisuals(pos,visuals)
476                                raw = hpack(raw,"exact",nil,direction)
477                                pos = hpack(pos,"exact",nil,direction)
478                                ctx_NC() context(f_u(first))
479                                ctx_NC() ctx_dontleavehmode() context(one)
480                                ctx_NC() context(single)
481                                ctx_NC() ctx_dontleavehmode() context(raw)
482                                ctx_NC() ctx_dontleavehmode() context(pos)
483                                ctx_NC() ctx_NR()
484                                done = true
485                            end
486                        end
487                        if done then
488                            ctx_stopSingle()
489                        end
490                        if null > 0 then
491                            if zero > 0 then
492                                ctx_type("null: %i, zero: %i",null,zero)
493                            else
494                                ctx_type("null: %i",null)
495                            end
496                        else
497                            if null > 0 then
498                                ctx_type("both zero: %i",zero)
499                            end
500                        end
501                    else
502                        -- todo
503                    end
504                end
505            end
506        end
507
508        local done = false
509
510        for d=1,#datasets do
511            local dataset  = datasets[d]
512            local sequence = dataset[3]
513            local kind     = sequence.type
514            if kind == "gpos_contextchain" or kind == "gpos_context" then
515                local steps = sequence.steps
516                for i=1,#steps do
517                    local step  = steps[i]
518                    local rules = step.rules
519                    if rules then
520                        for i=1,#rules do
521                            local rule = rules[i]
522                            local lookups = rule.lookups
523                            if lookups then
524                                for i=1,#lookups do
525                                    local lookup = lookups[i]
526                                    if lookup then
527                                        local look = lookup[1]
528                                        local dnik = look.type
529                                        if dnik == "gpos_pair" or dnik == "gpos_single" then
530                                            process(dataset,look,dnik,sequence.order,kind)
531                                        end
532                                    end
533                                end
534                            end
535                        end
536                    end
537                end
538                done = true
539            elseif kind == "gpos_pair" or kind == "gpos_single" then
540                process(dataset,sequence,kind)
541                done = true
542            end
543        end
544
545        if done then
546            return
547        end
548
549    end
550
551    nothing()
552
553end
554
555local dynamics = true
556
557function tabletracers.showsubstitutions(specification)
558
559    local tfmdata, fontid, resources = checked(specification)
560
561    if resources then
562        local features = resources.features
563        if features then
564            local gsub = features.gsub
565            if gsub then
566                local makes_sense = { }
567                for feature, scripts in sortedhash(gsub) do
568                    for script, languages in sortedhash(scripts) do
569                        for language in sortedhash(languages) do
570                            local tag = formatters["dummy-%s-%s-%s"](feature,script,language)
571                            local fnt = formatters["file:%s*%s"](file.basename(tfmdata.properties.filename),tag)
572                            context.definefontfeature (
573                                { tag },
574                                {
575                                    mode      = "node",
576                                    script    = script,
577                                    language  = language,
578                                    [feature] = "yes"
579                                }
580                            )
581                            if not dynamics then
582                                context.definefont( { fnt }, { fnt } )
583                            end
584                            makes_sense[#makes_sense+1] = {
585                                feature    = feature,
586                                tag        = tag,
587                                script     = script,
588                                language   = language,
589                                fontname   = fnt,
590                            }
591                        end
592                    end
593                end
594                if #makes_sense > 0 then
595                    context.starttabulate { "|Tl|Tl|Tl|p|" }
596                    for i=1,#makes_sense do
597                        local data     = makes_sense[i]
598                        local script   = data.script
599                        local language = data.language
600                        ctx_NC()
601                            context(data.feature)
602                        ctx_NC()
603                            context(script)
604                        ctx_NC()
605                            context(language)
606                        ctx_NC()
607                            if not dynamics then
608                                context.startfont { data.fontname }
609                            else
610                                context.addff(data.tag)
611                            end
612                            context.verbatim(samples.lowercase  [script][language]) context.par()
613                            context.verbatim(samples.uppercase  [script][language]) context.par()
614                            context.verbatim(samples.digits     [script][language]) context.par()
615                            context.verbatim(samples.punctuation[script][language]) context.quad()
616                            context.verbatim(samples.symbols    [script][language])
617                            if not dynamics then
618                                context.stopfont()
619                            end
620                        ctx_NC()
621                        ctx_NR()
622                    end
623                    context.stoptabulate()
624                    return
625                end
626            end
627        end
628    end
629
630    nothing()
631
632end
633
634function tabletracers.showunicodevariants(specification)
635
636    local tfmdata, fontid, resources = checked(specification)
637
638    if resources then
639
640        local variants  = fonts.hashes.variants[fontid]
641
642        if variants then
643            context.starttabulate { "|c|c|c|c|c|c|c|" }
644            for selector, unicodes in sortedhash(variants) do
645                local done = false
646                for unicode, variant in sortedhash(unicodes) do
647                    ctx_NC()
648                    if not done then
649                        context("%U",selector)
650                        done = true
651                    end
652                    ctx_NC()
653                    context("%U",unicode)
654                    ctx_NC()
655                    context("%c",unicode)
656                    ctx_NC()
657                    context("%U",variant)
658                    ctx_NC()
659                    context("%c",variant)
660                    ctx_NC()
661                    context("%c%c",unicode,selector)
662                    ctx_NC()
663                    context.startoverlay()
664                        context("{\\color[trace:r]{%c}}{\\color[trace:ds]{%c}}",unicode,variant)
665                    context.stopoverlay()
666                    ctx_NC()
667                    ctx_NR()
668                end
669            end
670            context.stoptabulate()
671            return
672        end
673
674    end
675
676    nothing()
677
678end
679
680
681local function collectligatures(steps)
682
683    -- Mostly the same as s-fonts-features so we should make a helper.
684
685    local series = { }
686    local stack  = { }
687    local max    = 0
688
689    local function add(v)
690        local n = #stack
691        if n > max then
692            max = n
693        end
694        series[#series+1] = { v, unpack(stack) }
695    end
696
697    local function make(tree)
698        for k, v in sortedhash(tree) do
699            if k == "ligature" then
700                add(v)
701            elseif tonumber(v) then
702                insert(stack,k)
703                add(v)
704                remove(stack)
705            else
706                insert(stack,k)
707                make(v)
708                remove(stack)
709            end
710        end
711    end
712
713    for i=1,#steps do
714        local step     = steps[i]
715        local coverage = step.coverage
716        if coverage then
717            make(coverage)
718        end
719    end
720
721    return series, max
722end
723
724local function banner(index,kind,order)
725    ctx_sequence("sequence: %i, kind: %s, features: % t",index,noprefix(kind),order)
726end
727
728function tabletracers.showligatures(specification)
729
730    local tfmdata, fontid, resources = checked(specification)
731
732    if resources then
733
734        local characters   = tfmdata.characters
735        local descriptions = tfmdata.descriptions
736        local sequences    = resources.sequences
737        if sequences then
738            local done = true
739            for index=1,#sequences do
740                local sequence = sequences[index]
741                local kind     = sequence.type
742                if kind == "gsub_ligature" then
743                    local list, max = collectligatures(sequence.steps)
744                    if #list > 0 then
745                        banner(index,kind,sequence.order or { })
746                        context.starttabulate { "|T|" .. string.rep("|",max) .. "|T|T|" }
747                        for i=1,#list do
748                            local s = list[i]
749                            local n = #s
750                            local u = s[1]
751                            local c = characters[u]
752                            local d = descriptions[u]
753                            ctx_NC()
754                            context("%U",u)
755                            ctx_NC()
756                            ctx_setfontid(fontid)
757                            ctx_char(u)
758                            ctx_NC()
759                            ctx_setfontid(fontid)
760                            for i=2,n do
761                                ctx_char(s[i])
762                                ctx_NC()
763                            end
764                            for i=n+1,max do
765                                ctx_NC()
766                            end
767                            context(d.name)
768                            ctx_NC()
769                            context(c.tounicode)
770                            ctx_NC()
771                            ctx_NR()
772                        end
773                        context.stoptabulate()
774                        done = true
775                    end
776                end
777            end
778            if done then
779                return
780            end
781        end
782    end
783
784    nothing()
785
786end
787