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