s-fonts-features.lua /size: 14 Kb    last modification: 2024-01-16 09:03
1if not modules then modules = { } end modules ['s-fonts-features'] = {
2    version   = 1.001,
3    comment   = "companion to s-fonts-features.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.features = moduledata.fonts.features or { }
11
12-- for the moment only otf
13
14local rawget = rawget
15local insert, remove, sortedhash = table.insert, table.remove, table.sortedhash
16
17local v_yes  = interfaces.variables.yes
18local v_no   = interfaces.variables.no
19local c_name = interfaces.constants.name
20
21local context = context
22local NC, NR, bold = context.NC, context.NR, context.bold
23
24function moduledata.fonts.features.showused(specification)
25
26    specification = interfaces.checkedspecification(specification)
27
28 -- local list = utilities.parsers.settings_to_set(specification.list or "all")
29
30    context.starttabulate { "|T|T|T|T|T|" }
31
32        context.HL()
33
34            NC() bold("feature")
35            NC()
36            NC() bold("description")
37            NC() bold("value")
38            NC() bold("internal")
39            NC() NR()
40
41        context.HL()
42
43            local usedfeatures = fonts.handlers.otf.statistics.usedfeatures
44            local features     = fonts.handlers.otf.tables.features
45            local descriptions = fonts.handlers.otf.features.descriptions
46
47            for feature, keys in sortedhash(usedfeatures) do
48             -- if list.all or (list.otf and rawget(features,feature)) or (list.extra and rawget(descriptions,feature)) then
49                    local done = false
50                    for k, v in sortedhash(keys) do
51                        if done then
52                            NC()
53                            NC()
54                            NC()
55                        elseif rawget(descriptions,feature) then
56                            NC() context(feature)
57                            NC() context("+") -- extra
58                            NC() context.escaped(descriptions[feature])
59                            done = true
60                        elseif rawget(features,feature) then
61                            NC() context(feature)
62                            NC()              -- otf
63                            NC() context.escaped(features[feature])
64                            done = true
65                        else
66                            NC() context(feature)
67                            NC() context("-") -- unknown
68                            NC()
69                            done = true
70                        end
71                        NC() context(k)
72                        NC() context(tostring(v))
73                        NC() NR()
74                    end
75             -- end
76            end
77
78        context.HL()
79
80    context.stoptabulate()
81
82end
83
84local function collectkerns(tfmdata,feature)
85    local combinations = { }
86    local resources    = tfmdata.resources
87    local characters   = tfmdata.characters
88    local sequences    = resources.sequences
89    local lookuphash   = resources.lookuphash
90    local feature      = feature or "kern"
91    if sequences then
92        for i=1,#sequences do
93            local sequence = sequences[i]
94            if sequence.features and sequence.features[feature] then
95                local steps = sequence.steps
96                for i=1,#steps do
97                    local step   = steps[i]
98                    local format = step.format
99                    for unicode, hash in table.sortedhash(step.coverage) do
100                        local kerns = combinations[unicode]
101                        if not kerns then
102                            kerns = { }
103                            combinations[unicode] = kerns
104                        end
105                        for otherunicode, kern in table.sortedhash(hash) do
106                            if format == "pair" then
107                                local f = kern[1]
108                                local s = kern[2]
109                                if f then
110                                    if s then
111                                        -- todo
112                                    else
113                                        if not kerns[otherunicode] and f[3] ~= 0 then
114                                            kerns[otherunicode] = f[3]
115                                        end
116                                    end
117                                elseif s then
118                                    -- todo
119                                end
120                            elseif format == "kern" then
121                                if not kerns[otherunicode] and kern ~= 0 then
122                                    kerns[otherunicode] = kern
123                                end
124                            end
125                        end
126                    end
127                end
128            end
129        end
130    end
131
132    return combinations
133end
134
135local showkernpair = context.showkernpair
136
137function moduledata.fonts.features.showbasekerns(specification)
138    -- assumes that the font is loaded in base mode
139    specification = interfaces.checkedspecification(specification)
140    local id, cs  = fonts.definers.internal(specification,"<module:fonts:features:font>")
141    local tfmdata = fonts.hashes.identifiers[id]
142    local done    = false
143    for unicode, character in sortedhash(tfmdata.characters) do
144        local kerns = character.kerns
145        if kerns then
146            context.par()
147            for othercode, kern in sortedhash(kerns) do
148                showkernpair(unicode,kern,othercode)
149            end
150            context.par()
151            done = true
152        end
153    end
154    if not done then
155        context("no kern pairs found")
156        context.par()
157    end
158end
159
160function moduledata.fonts.features.showallkerns(specification)
161    specification    = interfaces.checkedspecification(specification)
162    local id, cs     = fonts.definers.internal(specification,"<module:fonts:features:font>")
163    local tfmdata    = fonts.hashes.identifiers[id]
164    local allkerns   = collectkerns(tfmdata)
165    local characters = tfmdata.characters
166    local hfactor    = tfmdata.parameters.hfactor
167    if next(allkerns) then
168        for first, pairs in sortedhash(allkerns) do
169            context.par()
170            for second, kern in sortedhash(pairs) do
171             -- local kerns = characters[first].kerns
172             -- if not kerns and pairs[second] then
173             --     -- weird
174             -- end
175                showkernpair(first,kern*hfactor,second)
176            end
177            context.par()
178        end
179    else
180        context("no kern pairs found")
181        context.par()
182    end
183end
184
185function moduledata.fonts.features.showfeatureset(specification)
186    specification = interfaces.checkedspecification(specification)
187    local name = specification[c_name]
188    if name then
189        local s = fonts.specifiers.contextsetups[name]
190        if s then
191            local t = table.copy(s)
192            t.number = nil
193            if t and next(t) then
194                context.starttabulate { "|T|T|" }
195                    for k, v in sortedhash(t) do
196                       NC() context(k) NC() context(v == true and v_yes or v == false and v_no or tostring(v)) NC() NR()
197                    end
198                context.stoptabulate()
199            end
200        end
201    end
202end
203
204-- The next one looks a bit like the collector in font-oup.lua.
205
206local function collectligatures(tfmdata)
207    local sequences = tfmdata.resources.sequences
208
209    if not sequences then
210        return
211    end
212
213    -- Mostly the same as s-fonts-tables so we should make a helper.
214
215    local series = { }
216    local stack  = { }
217    local max    = 0
218
219    local function add(v)
220        local n = #stack
221        if n > max then
222            max = n
223        end
224        series[#series+1] = { v, unpack(stack) }
225    end
226
227    local function make(tree)
228        for k, v in sortedhash(tree) do
229            if k == "ligature" then
230                add(v)
231            elseif tonumber(v) then
232                insert(stack,k)
233                add(v)
234                remove(stack)
235            else
236                insert(stack,k)
237                make(v)
238                remove(stack)
239            end
240        end
241    end
242
243    for i=1,#sequences do
244        local sequence = sequences[i]
245        if sequence.type == "gsub_ligature" then
246            local steps = sequence.steps
247            for i=1,#steps do
248                local step     = steps[i]
249                local coverage = step.coverage
250                if coverage then
251                    make(coverage)
252                end
253            end
254        end
255    end
256
257    return series, max
258end
259
260function moduledata.fonts.features.showallligatures(specification)
261    specification      = interfaces.checkedspecification(specification)
262    local id, cs       = fonts.definers.internal(specification,"<module:fonts:features:font>")
263    local tfmdata      = fonts.hashes.identifiers[id]
264    local allligatures,
265          max          = collectligatures(tfmdata)
266    local characters   = tfmdata.characters
267    local descriptions = tfmdata.descriptions
268    if #allligatures > 0 then
269        context.starttabulate { "|T|" .. string.rep("|",max) .. "|T|T|" }
270        for i=1,#allligatures do
271            local s = allligatures[i]
272            local n = #s
273            local u = s[1]
274            local c = characters[u]
275            local d = descriptions[u]
276            NC()
277            context("%U",u)
278            NC()
279            context("\\setfontid%i\\relax",id)
280            context.char(u)
281            NC()
282            context("\\setfontid%i\\relax",id)
283            for i=2,n do
284                context.char(s[i])
285                NC()
286            end
287            for i=n+1,max do
288                NC()
289            end
290            context(d.name)
291            NC()
292            context(c.tounicode)
293            NC()
294            NR()
295        end
296        context.stoptabulate()
297    else
298        context("no ligatures found")
299        context.par()
300    end
301end
302
303
304function moduledata.fonts.features.showallfeatures(specification)
305    specification   = interfaces.checkedspecification(specification)
306    local id, cs    = fonts.definers.internal(specification,"<module:fonts:features:font>")
307    local tfmdata   = fonts.hashes.identifiers[id]
308    local sequences = tfmdata.resources.sequences
309
310    context.starttabulate { "|T|T|Tc|T|T|Tp|" }
311
312    NC() bold("\\letterhash")
313    NC() bold("type")
314    NC() bold("\\letterhash steps")
315    NC() bold("feature")
316    NC() bold("script")
317    NC() bold("language")
318    NC() NR()
319    context.HL()
320
321    for i=1,#sequences do
322        local s = sequences[i]
323        local features = s.features
324        if features then
325            local done1 = false
326            NC() context(i)
327            NC() context(s.type)
328            NC() context(s.nofsteps)
329            for feature, scripts in table.sortedhash(features) do
330                NC()
331                if done1 then
332                    NC() NC() NC()
333                else
334                    context(feature)
335                    done1 = true
336                end
337                local done2 = false
338                for script, languages in table.sortedhash(scripts) do
339                    if done2 then
340                        NC() NC() NC() NC()
341                    else
342                        done2 = true
343                    end
344                    NC() context(script)
345                    NC() context("% t",table.sortedkeys(languages))
346                    NC() NR()
347                end
348            end
349        else
350            NC() context(i)
351            NC() context(s.type)
352            NC() context(s.nofsteps)
353            NC() NC() NC() NC() NR()
354        end
355    end
356
357    context.stoptabulate()
358end
359
360local function collect(tfmdata,id,specification,feature)
361    local validlookups, lookuplist = fonts.handlers.otf.collectlookups(
362        tfmdata.shared.rawdata,
363        feature,
364        specification.script or "math",
365        specification.language or "dflt"
366    )
367    if lookuplist then
368        local descriptions = tfmdata.descriptions
369        for i=1,#lookuplist do
370            local lookup = lookuplist[i]
371            if lookup.type == "gsub_single" then
372                local steps = lookup.steps
373                context.startsubject { title = feature }
374                context.starttabulate { "|T|||T|" }
375                for i=1,lookup.nofsteps do
376                    local c = steps[i].coverage
377                    for k, v in table.sortedhash(c) do
378                        NC() context(descriptions[k].name)
379                        NC() context.showfontidchar(id,k)
380                        NC() context.showfontidchar(id,v)
381                        NC() context(descriptions[v].name)
382                        NC() NR()
383                    end
384                end
385                context.stoptabulate()
386                context.stopsubject()
387            elseif lookup.type == "gsub_alternate" then
388                local steps = lookup.steps
389                context.startsubject { title = feature }
390                context.starttabulate { "|T|||Tp|" }
391                for i=1,lookup.nofsteps do
392                    local c = steps[i].coverage
393                    for k, v in table.sortedhash(c) do
394                        NC() context(descriptions[k].name)
395                        NC() context.showfontidchar(id,k)
396                        NC()
397                        for i=1,#v do
398                            if i > 1 then context(" ") end
399                            context.showfontidchar(id,v[i])
400                        end
401                        NC()
402                        for i=1,#v do
403                            if i > 1 then context(" ") end
404                            context(descriptions[v[i]].name)
405                        end
406                        NC() NR()
407                    end
408                end
409                context.stoptabulate()
410                context.stopsubject()
411            end
412        end
413    end
414end
415
416function moduledata.fonts.features.showsubstitutions(specification)
417    specification = interfaces.checkedspecification(specification)
418    local id, cs  = fonts.definers.internal(specification,"<module:fonts:features:font>")
419    local tfmdata = fonts.hashes.identifiers[id]
420    local feature = specification.feature
421    if feature == "*" then
422        local features = tfmdata.shared.rawdata.resources.features
423        for k in table.sortedhash(features.gsub) do
424            collect(tfmdata,id,specification,k)
425        end
426    else
427        collect(tfmdata,id,specification,feature)
428    end
429end
430
431-- moduledata.fonts.features.showsubstitutions { name = "xcharter-math.otf", feature = "cv06", script = "math", language = "dflt" }
432-- moduledata.fonts.features.showsubstitutions { name = "xcharter-math.otf", feature = "*",    script = "math", language = "dflt" }
433
434