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