font-nod.lua /size: 12 Kb    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['font-nod'] = {
2    version   = 1.001,
3    optimize  = true,
4    comment   = "companion to font-ini.mkiv",
5    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
6    copyright = "PRAGMA ADE / ConTeXt Development Team",
7    license   = "see context related readme files"
8}
9
10local utfchar = utf.char
11local concat, fastcopy = table.concat, table.fastcopy
12local match, rep = string.match, string.rep
13
14fonts = fonts or { }
15nodes = nodes or { }
16
17local fonts            = fonts
18local nodes            = nodes
19local context          = context
20
21local tracers          = nodes.tracers or { }
22nodes.tracers          = tracers
23
24local tasks            = nodes.tasks or { }
25nodes.tasks            = tasks
26
27local handlers         = nodes.handlers or { }
28nodes.handlers         = handlers
29
30local nuts             = nodes.nuts
31local tonut            = nuts.tonut
32local tonode           = nuts.tonode
33
34local injections       = nodes.injections or { }
35nodes.injections       = injections
36
37local step_tracers     = tracers.steppers or { }
38tracers.steppers       = step_tracers
39
40local nodecodes        = nodes.nodecodes
41
42local glyph_code       = nodecodes.glyph
43local hlist_code       = nodecodes.hlist
44local vlist_code       = nodecodes.vlist
45local disc_code        = nodecodes.disc
46local glue_code        = nodecodes.glue
47local kern_code        = nodecodes.kern
48local dir_code         = nodecodes.dir
49local par_code         = nodecodes.par
50
51local getnext          = nuts.getnext
52local getprev          = nuts.getprev
53local getid            = nuts.getid
54local getfont          = nuts.getfont
55local getsubtype       = nuts.getsubtype
56local getlist          = nuts.getlist
57local getdisc          = nuts.getdisc
58local getreplace       = nuts.getreplace
59local isglyph          = nuts.isglyph
60local getkern          = nuts.getkern
61local getdirection     = nuts.getdirection
62local getwidth         = nuts.getwidth
63
64local setbox           = nuts.setbox
65local setchar          = nuts.setchar
66local setsubtype       = nuts.setsubtype
67
68local copy_node_list   = nuts.copylist
69local hpacknodelist    = nuts.hpack
70local flushnodelist    = nuts.flushlist
71local protectglyphs    = nuts.protectglyphs
72local startofpar       = nuts.startofpar
73
74local nextnode         = nuts.traversers.node
75local nextglyph        = nuts.traversers.glyph
76
77local nodepool         = nuts.pool
78local new_glyph        = nodepool.glyph
79
80local formatters       = string.formatters
81local formatter        = string.formatter
82
83local hashes           = fonts.hashes
84
85local fontidentifiers  = hashes.identifiers
86local fontdescriptions = hashes.descriptions
87local fontcharacters   = hashes.characters
88local fontproperties   = hashes.properties
89local fontparameters   = hashes.parameters
90
91local properties = nodes.properties.data
92
93local function freeze(h,where)
94    for n in nextnode, h do -- todo: disc but not traced anyway
95        local p = properties[n]
96        if p then
97            local i = p.injections         if i then p.injections        = fastcopy(i) end
98         -- local i = r.preinjections      if i then p.preinjections     = fastcopy(i) end
99         -- local i = r.postinjections     if i then p.postinjections    = fastcopy(i) end
100         -- local i = r.replaceinjections  if i then p.replaceinjections = fastcopy(i) end
101            -- only injections
102        end
103    end
104end
105
106local f_unicode = formatters["%U"]
107local f_badcode = formatters["{%i}"]
108
109local stack = { }
110
111function tracers.start(tag)
112    stack[#stack+1] = tag
113    local tracer = tracers[tag]
114    if tracer and tracer.start then
115        tracer.start()
116    end
117end
118function tracers.stop()
119    local tracer = stack[#stack]
120    if tracer and tracer.stop then
121        tracer.stop()
122    end
123    stack[#stack] = nil
124end
125
126-- experimental
127
128local collection, collecting, messages = { }, false, { }
129
130function step_tracers.start()
131    collecting = true
132end
133
134function step_tracers.stop()
135    collecting = false
136end
137
138function step_tracers.reset()
139    for i=1,#collection do
140        local c = collection[i]
141        if c then
142            flushnodelist(c)
143        end
144    end
145    collection, messages = { }, { }
146end
147
148function step_tracers.nofsteps()
149    return context(#collection)
150end
151
152function step_tracers.glyphs(n,i)
153    local c = collection[i]
154    if c then
155        local c = copy_node_list(c)
156        local b = hpacknodelist(c) -- multiple arguments
157        setbox(n,b)
158    end
159end
160
161function step_tracers.features()
162    local f = collection[1]
163    for n, char, font in nextglyph, f do
164        local tfmdata  = fontidentifiers[font]
165        local features = tfmdata.resources.features
166        local result_1 = { }
167        local result_2 = { }
168        local gpos = features and features.gpos or { }
169        local gsub = features and features.gsub or { }
170        for feature, value in table.sortedhash(tfmdata.shared.features) do
171            if feature == "number" or feature == "features" then
172                value = false
173            elseif type(value) == "boolean" then
174                if value then
175                    value = "yes"
176                else
177                    value = false
178                end
179            else
180                -- use value
181            end
182            if value then
183                if gpos[feature] or gsub[feature] or feature == "language" or feature == "script" then
184                    result_1[#result_1+1] = formatters["%s=%s"](feature,value)
185                else
186                    result_2[#result_2+1] = formatters["%s=%s"](feature,value)
187                end
188            end
189        end
190        if #result_1 > 0 then
191            context("{\\bf[basic:} %, t{\\bf]} ",result_1)
192        else
193            context("{\\bf[}no basic features{\\bf]} ")
194        end
195        if #result_2 > 0 then
196            context("{\\bf[extra:} %, t{\\bf]}",result_2)
197        else
198            context("{\\bf[}no extra features{\\bf]}")
199        end
200        return
201    end
202end
203
204function tracers.fontchar(font,char)
205    local n = new_glyph(font,char)
206    setsubtype(n,256)
207    context(tonode(n))
208end
209
210function step_tracers.font(command)
211    local c = collection[1]
212    for n, char, font in nextglyph, c do
213        local name = file.basename(fontproperties[font].filename or "unknown")
214        local size = fontparameters[font].size or 0
215        if command then
216            context[command](font,name,size) -- size in sp
217        else
218            context("[%s: %s @ %p]",font,name,size)
219        end
220        return
221    end
222end
223
224local colors = {
225    pre     = { "darkred" },
226    post    = { "darkgreen" },
227    replace = { "darkblue" },
228}
229
230function step_tracers.codes(i,command,space)
231    local c = collection[i]
232
233    local function showchar(c,f)
234        if command then
235            local d = fontdescriptions[f]
236            local d = d and d[c]
237            context[command](f,c,d and d.class or "")
238        else
239            context("[%s:U+%X]",f,c)
240        end
241    end
242
243    local function showdisc(d,w,what)
244        if w then
245            context.startcolor(colors[what])
246            context("%s:",what)
247            for c, id in nextnode, w do
248                if id == glyph_code then
249                    local c, f = isglyph(c)
250                    showchar(c,f)
251                else
252                    context("[%s]",nodecodes[id])
253                end
254            end
255            context[space]()
256            context.stopcolor()
257        end
258    end
259
260    while c do
261        local char, id = isglyph(c)
262        if char then
263            showchar(char,id)
264        elseif id == dir_code or (id == par_code and startofpar(c)) then
265            context("[%s]",getdirection(c) or "?")
266        elseif id == disc_code then
267            local pre, post, replace = getdisc(c)
268            if pre or post or replace then
269                context("[")
270                context[space]()
271                showdisc(c,pre,"pre")
272                showdisc(c,post,"post")
273                showdisc(c,replace,"replace")
274                context[space]()
275                context("]")
276            else
277                context("[disc]")
278            end
279        else
280            context("[%s]",nodecodes[id])
281        end
282        c = getnext(c)
283    end
284end
285
286function step_tracers.messages(i,command,split)
287    local list = messages[i] -- or { "no messages" }
288    if list then
289        for i=1,#list do
290            local l = list[i]
291            if not command then
292                context("(%s)",l)
293            elseif split then
294                local a, b = match(l,"^(.-)%s*:%s*(.*)$")
295                context[command](a or l or "",b or "")
296            else
297                context[command](l)
298            end
299        end
300    end
301end
302
303-- hooks into the node list processor (see otf)
304
305function step_tracers.check(head)
306    if collecting then
307        step_tracers.reset()
308        local n = copy_node_list(head)
309        freeze(n,"check")
310        injections.keepcounts() -- one-time
311        local l = injections.handler(n,"trace")
312        if l then -- hm, can be false
313            n = l
314        end
315        protectglyphs(n)
316        collection[1] = n
317    end
318end
319
320function step_tracers.register(head)
321    if collecting then
322        local nc = #collection+1
323        if messages[nc] then
324            local n = copy_node_list(head)
325            freeze(n,"register")
326            injections.keepcounts() -- one-time
327            local l = injections.handler(n,"trace")
328            if l then -- hm, can be false
329                n = l
330            end
331            protectglyphs(n)
332            collection[nc] = n
333        end
334    end
335end
336
337function step_tracers.message(str,...)
338    str = formatter(str,...)
339    if collecting then
340        local n = #collection + 1
341        local m = messages[n]
342        if not m then m = { } messages[n] = m end
343        m[#m+1] = str
344    end
345    return str -- saves an intermediate var in the caller
346end
347
348--
349
350local threshold = 65536 -- 1pt
351
352local function toutf(list,result,nofresult,stopcriterium,nostrip)
353    if list then
354        for n, id in nextnode, tonut(list) do
355            if id == glyph_code then
356                local c, f = isglyph(n)
357                if c > 0 then
358                    local fc = fontcharacters[f]
359                    if fc then
360                        local fcc = fc[c]
361                        if fcc then
362                            local u = fcc.unicode
363                            if not u then
364                                nofresult = nofresult + 1
365                                result[nofresult] = utfchar(c)
366                            elseif type(u) == "table" then
367                                for i=1,#u do
368                                    nofresult = nofresult + 1
369                                    result[nofresult] = utfchar(u[i])
370                                end
371                            else
372                                nofresult = nofresult + 1
373                                result[nofresult] = utfchar(u)
374                            end
375                        else
376                            nofresult = nofresult + 1
377                            result[nofresult] = utfchar(c)
378                        end
379                    else
380                        nofresult = nofresult + 1
381                        result[nofresult] = f_unicode(c)
382                    end
383                else
384                    nofresult = nofresult + 1
385                    result[nofresult] = f_badcode(c)
386                end
387            elseif id == disc_code then
388                local replace = getreplace(n)
389                result, nofresult = toutf(replace,result,nofresult,false,true) -- needed?
390            elseif id == hlist_code or id == vlist_code then
391             -- if nofresult > 0 and result[nofresult] ~= " " then
392             --     nofresult = nofresult + 1
393             --     result[nofresult] = " "
394             -- end
395                result, nofresult = toutf(getlist(n),result,nofresult,false,true)
396            elseif id == glue_code then
397                if nofresult > 0 and result[nofresult] ~= " " and getwidth(n) > threshold then
398                    nofresult = nofresult + 1
399                    result[nofresult] = " "
400                end
401            elseif id == kern_code then
402                if nofresult > 0 and result[nofresult] ~= " " and getkern(n) > threshold then
403                    nofresult = nofresult + 1
404                    result[nofresult] = " "
405                end
406            end
407            if n == stopcriterium then
408                break
409            end
410        end
411    end
412    if not nostrip and nofresult > 0 and result[nofresult] == " " then
413        result[nofresult] = nil
414        nofresult = nofresult - 1
415    end
416    return result, nofresult
417end
418
419function nodes.toutf(list,stopcriterium)
420    local result, nofresult = toutf(list,{},0,stopcriterium)
421    return concat(result)
422end
423