math-noa.lmt /size: 86 Kb    last modification: 2021-10-28 13:51
1if not modules then modules = { } end modules ['math-noa'] = {
2    version   = 1.001,
3    optimize  = true,
4    comment   = "companion to math-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-- beware: this is experimental code and there will be a more generic (attribute value
11-- driven) interface too but for the moment this is ok (sometime in 2015-2016 i will
12-- start cleaning up as by then the bigger picture is clear and code has been used for
13-- years; the main handlers will get some extensions)
14--
15-- we will also make dedicated processors (faster)
16--
17-- beware: names will change as we wil make noads.xxx.handler i.e. xxx
18-- subnamespaces
19
20-- 20D6 -> 2190
21-- 20D7 -> 2192
22
23-- todo: most is mathchar_code so we can have simple dedicated loops
24
25-- nota bene: uunderdelimiter uoverdelimiter etc are radicals (we have 5 types)
26
27local next, tonumber = next, tonumber
28local utfchar, utfbyte = utf.char, utf.byte
29local formatters, gmatch = string.formatters, string.gmatch
30local sortedhash = table.sortedhash
31local insert, remove = table.insert, table.remove
32local div, round = math.div, math.round
33
34local fonts              = fonts
35local nodes              = nodes
36local node               = node
37local mathematics        = mathematics
38local context            = context
39
40local otf                = fonts.handlers.otf
41local otffeatures        = fonts.constructors.features.otf
42local registerotffeature = otffeatures.register
43
44local privateattribute   = attributes.private
45local registertracker    = trackers.register
46local registerdirective  = directives.register
47local logreporter        = logs.reporter
48local setmetatableindex  = table.setmetatableindex
49
50local texgetmode         = tex.getmode
51local mathmode_code      = tex.modelevels.math
52
53local colortracers       = nodes.tracers.colors
54
55local trace_remapping    = false  registertracker("math.remapping",   function(v) trace_remapping   = v end)
56local trace_processing   = false  registertracker("math.processing",  function(v) trace_processing  = v end)
57local trace_analyzing    = false  registertracker("math.analyzing",   function(v) trace_analyzing   = v end)
58local trace_normalizing  = false  registertracker("math.normalizing", function(v) trace_normalizing = v end)
59local trace_collapsing   = false  registertracker("math.collapsing",  function(v) trace_collapsing  = v end)
60local trace_fixing       = false  registertracker("math.fixing",      function(v) trace_fixing      = v end)
61local trace_patching     = false  registertracker("math.patching",    function(v) trace_patching    = v end)
62local trace_goodies      = false  registertracker("math.goodies",     function(v) trace_goodies     = v end)
63local trace_variants     = false  registertracker("math.variants",    function(v) trace_variants    = v end)
64local trace_alternates   = false  registertracker("math.alternates",  function(v) trace_alternates  = v end)
65local trace_italics      = false  registertracker("math.italics",     function(v) trace_italics     = v end)
66local trace_kernpairs    = false  registertracker("math.kernpairs",   function(v) trace_kernpairs   = v end)
67local trace_domains      = false  registertracker("math.domains",     function(v) trace_domains     = v end)
68local trace_families     = false  registertracker("math.families",    function(v) trace_families    = v end)
69local trace_fences       = false  registertracker("math.fences",      function(v) trace_fences      = v end)
70local trace_unstacking   = false  registertracker("math.unstack",     function(v) trace_unstacking  = v end)
71
72local check_coverage     = true   registerdirective("math.checkcoverage",  function(v) check_coverage  = v end)
73
74local report_processing  = logreporter("mathematics","processing")
75local report_remapping   = logreporter("mathematics","remapping")
76local report_normalizing = logreporter("mathematics","normalizing")
77local report_collapsing  = logreporter("mathematics","collapsing")
78local report_fixing      = logreporter("mathematics","fixing")
79local report_patching    = logreporter("mathematics","patching")
80local report_goodies     = logreporter("mathematics","goodies")
81local report_variants    = logreporter("mathematics","variants")
82local report_alternates  = logreporter("mathematics","alternates")
83local report_italics     = logreporter("mathematics","italics")
84local report_kernpairs   = logreporter("mathematics","kernpairs")
85local report_domains     = logreporter("mathematics","domains")
86local report_families    = logreporter("mathematics","families")
87local report_fences      = logreporter("mathematics","fences")
88local report_unstacking  = logreporter("mathematics","unstack")
89
90local a_mathrendering    = privateattribute("mathrendering")
91local a_exportstatus     = privateattribute("exportstatus")
92
93local nuts               = nodes.nuts
94local nodepool           = nuts.pool
95local tonut              = nuts.tonut
96local nutstring          = nuts.tostring
97
98local setfield           = nuts.setfield
99local setlink            = nuts.setlink
100local setlist            = nuts.setlist
101local setnext            = nuts.setnext
102local setprev            = nuts.setprev
103local setchar            = nuts.setchar
104local setfam             = nuts.setfam
105local setsubtype         = nuts.setsubtype
106local setattr            = nuts.setattr
107local setattrlist        = nuts.setattrlist
108local setwidth           = nuts.setwidth
109local setheight          = nuts.setheight
110local setdepth           = nuts.setdepth
111
112local getfield           = nuts.getfield
113local getnext            = nuts.getnext
114local getprev            = nuts.getprev
115local getboth            = nuts.getboth
116local getid              = nuts.getid
117local getsubtype         = nuts.getsubtype
118local getchar            = nuts.getchar
119local getfont            = nuts.getfont
120local getfam             = nuts.getfam
121local getcharspec        = nuts.getcharspec
122local getattr            = nuts.getattr
123local getattrs           = nuts.getattrs
124local getlist            = nuts.getlist
125local getwidth           = nuts.getwidth
126local getheight          = nuts.getheight
127local getdepth           = nuts.getdepth
128
129local getnucleus         = nuts.getnucleus
130local getsub             = nuts.getsub
131local getsup             = nuts.getsup
132local getsubpre          = nuts.getsubpre
133local getsuppre          = nuts.getsuppre
134
135local setnucleus         = nuts.setnucleus
136local setsub             = nuts.setsub
137local setsup             = nuts.setsup
138local setsubpre          = nuts.setsubpre
139local setsuppre          = nuts.setsuppre
140
141local flushnode          = nuts.flush
142local copy_node          = nuts.copy
143local slide_nodes        = nuts.slide
144local set_visual         = nuts.setvisual
145
146local mlisttohlist       = nuts.mlisttohlist
147
148local new_kern           = nodepool.kern
149local new_submlist       = nodepool.submlist
150local new_noad           = nodepool.noad
151local new_delimiter      = nodepool.delimiter
152local new_fence          = nodepool.fence
153
154local fonthashes         = fonts.hashes
155local fontdata           = fonthashes.identifiers
156local fontcharacters     = fonthashes.characters
157local fontitalics        = fonthashes.italics
158local fontparameters     = fonthashes.parameters
159
160local variables          = interfaces.variables
161local texsetattribute    = tex.setattribute
162local texgetattribute    = tex.getattribute
163local getfontoffamily    = tex.getfontoffamily
164local unsetvalue         = attributes.unsetvalue
165local implement          = interfaces.implement
166
167local v_reset            = variables.reset
168
169local chardata           = characters.data
170
171noads                    = noads or { }  -- todo: only here
172local noads              = noads
173
174noads.processors         = noads.processors or { }
175local processors         = noads.processors
176
177noads.handlers           = noads.handlers   or { }
178local handlers           = noads.handlers
179
180local tasks              = nodes.tasks
181local enableaction       = tasks.enableaction
182local setaction          = tasks.setaction
183
184local nodecodes          = nodes.nodecodes
185local noadcodes          = nodes.noadcodes
186local fencecodes         = nodes.fencecodes
187
188local ordnoad_code       = noadcodes.ord
189local opnoad_code        = noadcodes.op
190local binnoad_code       = noadcodes.bin
191local relnode_code       = noadcodes.rel
192local opennoad_code      = noadcodes.open
193local closenoad_code     = noadcodes.close
194local punctnoad_code     = noadcodes.punct
195local innernoad_code     = noadcodes.inner
196local undernoad_code     = noadcodes.under
197local overnoad_code      = noadcodes.over
198local vcenternoad_code   = noadcodes.vcenter
199
200local noad_code          = nodecodes.noad           -- attr nucleus sub sup
201local accent_code        = nodecodes.accent         -- attr nucleus sub sup accent
202local radical_code       = nodecodes.radical        -- attr nucleus sub sup left degree
203local fraction_code      = nodecodes.fraction       -- attr nucleus sub sup left right
204local subbox_code        = nodecodes.subbox         -- attr list
205local submlist_code      = nodecodes.submlist       -- attr list
206local mathchar_code      = nodecodes.mathchar       -- attr fam char
207local mathtextchar_code  = nodecodes.mathtextchar   -- attr fam char
208local delimiter_code     = nodecodes.delimiter      -- attr small_fam small_char large_fam large_char
209----- style_code         = nodecodes.style          -- attr style
210----- parameter_code     = nodecodes.parameter      -- attr style
211local math_choice        = nodecodes.choice         -- attr display text script scriptscript
212local fence_code         = nodecodes.fence          -- attr subtype
213
214local leftfence_code     = fencecodes.left
215local middlefence_code   = fencecodes.middle
216local rightfence_code    = fencecodes.right
217
218-- local mathclasses          = mathematics.classes
219-- local fenceclasses         = {
220--     [leftfence_code]   = mathclasses.open,
221--     [middlefence_code] = mathclasses.middle,
222--     [rightfence_code]  = mathclasses.close,
223-- }
224
225-- this initial stuff is tricky as we can have removed and new nodes with the same address
226-- the only way out is a free-per-page list of nodes (not bad anyway)
227
228-- local gf = getfield local gt = setmetatableindex("number") getfield = function(n,f)   gt[f] = gt[f] + 1 return gf(n,f)   end mathematics.GETFIELD = gt
229-- local sf = setfield local st = setmetatableindex("number") setfield = function(n,f,v) st[f] = st[f] + 1        sf(n,f,v) end mathematics.SETFIELD = st
230
231local function process(start,what,n,parent)
232
233    if n then
234        n = n + 1
235    else
236        n = 0
237    end
238    --
239    local initial = start
240    --
241    slide_nodes(start) -- we still miss a prev in noads -- fences test code
242    --
243    while start do
244        local id = getid(start)
245        if trace_processing then
246            if id == noad_code then
247                report_processing("%w%S, class %a",n*2,nutstring(start),noadcodes[getsubtype(start)])
248            elseif id == mathchar_code then
249                local char, font, fam = getcharspec(start)
250                report_processing("%w%S, family %a, font %a, char %a, shape %c",n*2,nutstring(start),fam,font,char,char)
251            else
252                report_processing("%w%S",n*2,nutstring(start))
253            end
254        end
255        local proc = what[id]
256        if proc then
257         -- report_processing("start processing")
258            local done, newstart, newinitial = proc(start,what,n,parent) -- prev is bugged:  or getprev(start)
259            if newinitial then
260                initial = newinitial -- temp hack .. we will make all return head
261                if newstart then
262                    start = newstart
263                 -- report_processing("stop processing (new start)")
264                else
265                 -- report_processing("quit processing (done)")
266                    break
267                end
268            else
269                if newstart then
270                    start = newstart
271                 -- report_processing("stop processing (new start)")
272                else
273                 -- report_processing("stop processing")
274                end
275            end
276        elseif id == noad_code then
277            -- single characters are like this
278            local noad = getnucleus(start)              if noad then process(noad,what,n,start) end -- list
279                  noad = getsup    (start)              if noad then process(noad,what,n,start) end -- list
280                  noad = getsub    (start)              if noad then process(noad,what,n,start) end -- list
281                if getsubpre then
282                  noad = getsuppre (start)              if noad then process(noad,what,n,start) end -- list
283                  noad = getsubpre (start)              if noad then process(noad,what,n,start) end -- list
284                end
285        elseif id == mathchar_code or id == mathtextchar_code or id == delimiter_code then
286            break
287        elseif id == subbox_code or id == submlist_code then
288            local noad = getlist(start)                 if noad then process(noad,what,n,start) end -- list (not getlist !)
289        elseif id == fraction_code then
290            local noad = getfield(start,"num")          if noad then process(noad,what,n,start) end -- list
291                  noad = getfield(start,"denom")        if noad then process(noad,what,n,start) end -- list
292                  noad = getfield(start,"left")         if noad then process(noad,what,n,start) end -- delimiter
293                  noad = getfield(start,"right")        if noad then process(noad,what,n,start) end -- delimiter
294        elseif id == math_choice then
295            local noad = getfield(start,"display")      if noad then process(noad,what,n,start) end -- list
296                  noad = getfield(start,"text")         if noad then process(noad,what,n,start) end -- list
297                  noad = getfield(start,"script")       if noad then process(noad,what,n,start) end -- list
298                  noad = getfield(start,"scriptscript") if noad then process(noad,what,n,start) end -- list
299        elseif id == fence_code then
300            local noad = getfield(start,"delimiter")    if noad then process(noad,what,n,start) end -- delimiter
301        elseif id == radical_code then
302            local noad = getnucleus(start)              if noad then process(noad,what,n,start) end -- list
303                  noad = getsup    (start)              if noad then process(noad,what,n,start) end -- list
304                  noad = getsub    (start)              if noad then process(noad,what,n,start) end -- list
305                if getsubpre then
306                  noad = getsuppre (start)              if noad then process(noad,what,n,start) end -- list
307                  noad = getsubpre (start)              if noad then process(noad,what,n,start) end -- list
308                end
309                  noad = getfield(start,"left")         if noad then process(noad,what,n,start) end -- delimiter
310                  noad = getfield(start,"degree")       if noad then process(noad,what,n,start) end -- list
311        elseif id == accent_code then
312            local noad = getnucleus(start)              if noad then process(noad,what,n,start) end -- list
313                  noad = getsup    (start)              if noad then process(noad,what,n,start) end -- list
314                  noad = getsub    (start)              if noad then process(noad,what,n,start) end -- list
315                if getsubpre then
316                  noad = getsuppre (start)              if noad then process(noad,what,n,start) end -- list
317                  noad = getsubpre (start)              if noad then process(noad,what,n,start) end -- list
318                end
319                  noad = getfield(start,"accent")       if noad then process(noad,what,n,start) end -- list
320                  noad = getfield(start,"bot_accent")   if noad then process(noad,what,n,start) end -- list
321     -- elseif id == style_code then
322     --     -- has a next
323     -- elseif id == parameter_code then
324     --     -- has a next
325     -- else
326     --     -- glue, penalty, etc
327        end
328        start = getnext(start)
329    end
330    if not parent then
331        return initial -- only first level -- for now
332    end
333end
334
335local function processnested(current,what,n)
336    local noad = nil
337    local id   = getid(current)
338    if id == noad_code then
339        noad = getnucleus(current)              if noad then process(noad,what,n,current) end -- list
340        noad = getsup    (current)              if noad then process(noad,what,n,current) end -- list
341        noad = getsub    (current)              if noad then process(noad,what,n,current) end -- list
342      if getsubpre then
343        noad = getsuppre (current)              if noad then process(noad,what,n,current) end -- list
344        noad = getsubpre (current)              if noad then process(noad,what,n,current) end -- list
345      end
346    elseif id == subbox_code or id == submlist_code then
347        noad = getlist(current)                 if noad then process(noad,what,n,current) end -- list (not getlist !)
348    elseif id == fraction_code then
349        noad = getfield(current,"num")          if noad then process(noad,what,n,current) end -- list
350        noad = getfield(current,"denom")        if noad then process(noad,what,n,current) end -- list
351        noad = getfield(current,"left")         if noad then process(noad,what,n,current) end -- delimiter
352        noad = getfield(current,"right")        if noad then process(noad,what,n,current) end -- delimiter
353    elseif id == math_choice then
354        noad = getfield(current,"display")      if noad then process(noad,what,n,current) end -- list
355        noad = getfield(current,"text")         if noad then process(noad,what,n,current) end -- list
356        noad = getfield(current,"script")       if noad then process(noad,what,n,current) end -- list
357        noad = getfield(current,"scriptscript") if noad then process(noad,what,n,current) end -- list
358    elseif id == fence_code then
359        noad = getfield(current,"delimiter")    if noad then process(noad,what,n,current) end -- delimiter
360    elseif id == radical_code then
361        noad = getnucleus(current)              if noad then process(noad,what,n,current) end -- list
362        noad = getsup    (current)              if noad then process(noad,what,n,current) end -- list
363        noad = getsub    (current)              if noad then process(noad,what,n,current) end -- list
364      if getsubpre then
365        noad = getsuppre (current)              if noad then process(noad,what,n,current) end -- list
366        noad = getsubpre (current)              if noad then process(noad,what,n,current) end -- list
367      end
368        noad = getfield(current,"left")         if noad then process(noad,what,n,current) end -- delimiter
369        noad = getfield(current,"degree")       if noad then process(noad,what,n,current) end -- list
370    elseif id == accent_code then
371        noad = getnucleus(current)              if noad then process(noad,what,n,current) end -- list
372        noad = getsup    (current)              if noad then process(noad,what,n,current) end -- list
373        noad = getsub    (current)              if noad then process(noad,what,n,current) end -- list
374      if getsubpre then
375        noad = getsuppre (current)              if noad then process(noad,what,n,current) end -- list
376        noad = getsubpre (current)              if noad then process(noad,what,n,current) end -- list
377      end
378        noad = getfield(current,"accent")       if noad then process(noad,what,n,current) end -- list
379        noad = getfield(current,"bot_accent")   if noad then process(noad,what,n,current) end -- list
380    end
381end
382
383local function processstep(current,process,n,id)
384    local noad = nil
385    local id   = id or getid(current)
386    if id == noad_code then
387        noad = getnucleus(current)              if noad then process(noad,n,current) end -- list
388        noad = getsup    (current)              if noad then process(noad,n,current) end -- list
389        noad = getsub    (current)              if noad then process(noad,n,current) end -- list
390      if getsubpre then
391        noad = getsuppre (current)              if noad then process(noad,n,current) end -- list
392        noad = getsubpre (current)              if noad then process(noad,n,current) end -- list
393      end
394    elseif id == subbox_code or id == submlist_code then
395        noad = getlist(current)                 if noad then process(noad,n,current) end -- list (not getlist !)
396    elseif id == fraction_code then
397        noad = getfield(current,"num")          if noad then process(noad,n,current) end -- list
398        noad = getfield(current,"denom")        if noad then process(noad,n,current) end -- list
399        noad = getfield(current,"left")         if noad then process(noad,n,current) end -- delimiter
400        noad = getfield(current,"right")        if noad then process(noad,n,current) end -- delimiter
401    elseif id == math_choice then
402        noad = getfield(current,"display")      if noad then process(noad,n,current) end -- list
403        noad = getfield(current,"text")         if noad then process(noad,n,current) end -- list
404        noad = getfield(current,"script")       if noad then process(noad,n,current) end -- list
405        noad = getfield(current,"scriptscript") if noad then process(noad,n,current) end -- list
406    elseif id == fence_code then
407        noad = getfield(current,"delimiter")    if noad then process(noad,n,current) end -- delimiter
408    elseif id == radical_code then
409        noad = getnucleus(current)              if noad then process(noad,n,current) end -- list
410        noad = getsup    (current)              if noad then process(noad,n,current) end -- list
411        noad = getsub    (current)              if noad then process(noad,n,current) end -- list
412      if getsubpre then
413        noad = getsuppre (current)              if noad then process(noad,n,current) end -- list
414        noad = getsubpre (current)              if noad then process(noad,n,current) end -- list
415      end
416        noad = getfield(current,"left")         if noad then process(noad,n,current) end -- delimiter
417        noad = getfield(current,"degree")       if noad then process(noad,n,current) end -- list
418    elseif id == accent_code then
419        noad = getnucleus(current)              if noad then process(noad,n,current) end -- list
420        noad = getsup    (current)              if noad then process(noad,n,current) end -- list
421        noad = getsub    (current)              if noad then process(noad,n,current) end -- list
422      if getsubpre then
423        noad = getsuppre (current)              if noad then process(noad,n,current) end -- list
424        noad = getsubpre (current)              if noad then process(noad,n,current) end -- list
425      end
426        noad = getfield(current,"accent")       if noad then process(noad,n,current) end -- list
427        noad = getfield(current,"bot_accent")   if noad then process(noad,n,current) end -- list
428    end
429end
430
431local function processnoads(head,actions,banner)
432    if trace_processing then
433        report_processing("start %a",banner)
434        head = process(head,actions)
435        report_processing("stop %a",banner)
436    else
437        head = process(head,actions)
438    end
439    return head
440end
441
442noads.process       = processnoads
443noads.processnested = processnested
444noads.processouter  = process
445
446-- experiment (when not present fall back to fam 0) -- needs documentation
447
448local unknowns = { }
449local checked  = { } -- simple case
450local tracked  = false  trackers.register("fonts.missing", function(v) tracked = v end)
451local cached   = setmetatableindex("table") -- complex case
452
453local function errorchar(font,char)
454    local done = unknowns[char]
455    if done then
456        unknowns[char] = done  + 1
457    else
458        unknowns[char] = 1
459    end
460    if tracked then
461        -- slower as we check each font too and we always replace as math has
462        -- more demands than text
463        local fake = cached[font][char]
464        if fake then
465            return fake
466        else
467            local kind, fake = fonts.checkers.placeholder(font,char)
468            if not fake or kind ~= "char" then -- Also check for "with" here?
469                fake = 0x3F
470            end
471            cached[font][char] = fake
472            return fake
473        end
474    else
475        -- only simple checking, report at the end so one should take
476        -- action anyway ... we can miss a few checks but that is ok
477        -- as there is at least one reported
478        if not checked[char] then
479            if trace_normalizing then
480                report_normalizing("character %C is not available",char)
481            end
482            checked[char] = true
483        end
484        return 0x3F
485    end
486end
487
488-- 0-2 regular
489-- 3-5 bold
490-- 6-8 pseudobold
491
492-- this could best be integrated in the remapper, and if we run into problems, we
493-- might as well do this
494
495do
496
497    local families     = { }
498    local a_mathfamily = privateattribute("mathfamily")
499    local boldmap      = mathematics.boldmap
500
501    local familymap = { [0] =
502        "regular",
503        "regular",
504        "regular",
505        "bold",
506        "bold",
507        "bold",
508        "pseudobold",
509        "pseudobold",
510        "pseudobold",
511    }
512
513    families[fraction_code] = function(pointer,what,n,parent)
514        local a = getattr(pointer,a_mathfamily)
515        if a and a >= 0 then
516            if a > 0 then
517                setattr(pointer,a_mathfamily,0)
518                if a > 5 then
519                    a = a - 3
520                end
521            end
522            setfam(pointer,a)
523        end
524        processnested(pointer,families,n+1)
525    end
526
527    families[noad_code] = function(pointer,what,n,parent)
528        local a = getattr(pointer,a_mathfamily)
529        if a and a >= 0 then
530            if a > 0 then
531                setattr(pointer,a_mathfamily,0)
532                if a > 5 then
533                    a = a - 3
534                end
535            end
536            setfam(pointer,a)
537        end
538        processnested(pointer,families,n+1)
539    end
540
541    families[mathchar_code] = function(pointer)
542        if getfam(pointer) == 0 then
543            local a = getattr(pointer,a_mathfamily)
544            if a and a > 0 then
545                setattr(pointer,a_mathfamily,0)
546                if a > 5 then
547                    local char = getchar(pointer)
548                    local bold = boldmap[char]
549                    local newa = a - 3
550                    if not bold then
551                        if trace_families then
552                            report_families("no bold replacement for %C, family %s with remap %s becomes %s with remap %s",char,a,familymap[a],newa,familymap[newa])
553                        end
554                        setfam(pointer,newa)
555                    elseif not fontcharacters[getfontoffamily(newa)][bold] then
556                        if trace_families then
557                            report_families("no bold character for %C, family %s with remap %s becomes %s with remap %s",char,a,familymap[a],newa,familymap[newa])
558                        end
559                        if newa > 3 then
560                            setfam(pointer,newa-3)
561                        end
562                    else
563                        setattr(pointer,a_exportstatus,char)
564                        setchar(pointer,bold)
565                        if trace_families then
566                            report_families("replacing %C by bold %C, family %s with remap %s becomes %s with remap %s",char,bold,a,familymap[a],newa,familymap[newa])
567                        end
568                        setfam(pointer,newa)
569                    end
570                else
571                    local char = getchar(pointer)
572                    if not fontcharacters[getfontoffamily(a)][char] then
573                        if trace_families then
574                            report_families("no bold replacement for %C",char)
575                        end
576                    else
577                        if trace_families then
578                            report_families("family of %C becomes %s with remap %s",char,a,familymap[a])
579                        end
580                        setfam(pointer,a)
581                    end
582                end
583            end
584        end
585    end
586    families[delimiter_code] = function(pointer)
587        if getfield(pointer,"small_fam") == 0 then
588            local a = getattr(pointer,a_mathfamily)
589            if a and a > 0 then
590                setattr(pointer,a_mathfamily,0)
591                if a > 5 then
592                    -- no bold delimiters in unicode
593                    a = a - 3
594                end
595                local fam  = getfontoffamily(a)
596                local char = getfield(pointer,"small_char")
597                local okay = fontcharacters[fam][char]
598                if okay then
599                    setfield(pointer,"small_fam",a)
600                elseif a > 2 then
601                    setfield(pointer,"small_fam",a-3)
602                end
603                local char = getfield(pointer,"large_char")
604                local okay = fontcharacters[fam][char]
605                if okay then
606                    setfield(pointer,"large_fam",a)
607                elseif a > 2 then
608                    setfield(pointer,"large_fam",a-3)
609                end
610            else
611                setfield(pointer,"small_fam",0)
612                setfield(pointer,"large_fam",0)
613            end
614        end
615    end
616
617    -- will become:
618
619    -- families[delimiter_code] = function(pointer)
620    --     if getfam(pointer) == 0 then
621    --         local a = getattr(pointer,a_mathfamily)
622    --         if a and a > 0 then
623    --             setattr(pointer,a_mathfamily,0)
624    --             if a > 5 then
625    --                 -- no bold delimiters in unicode
626    --                 a = a - 3
627    --             end
628    --             local char = getchar(pointer)
629    --             local okay = fontcharacters[getfontoffamily(a)][char]
630    --             if okay then
631    --                 setfam(pointer,a)
632    --             elseif a > 2 then
633    --                 setfam(pointer,a-3)
634    --             end
635    --         else
636    --             setfam(pointer,0)
637    --         end
638    --     end
639    -- end
640
641    families[mathtextchar_code] = families[mathchar_code]
642
643    function handlers.families(head,style,penalties)
644        processnoads(head,families,"families")
645        return true -- not needed
646    end
647
648end
649
650-- character remapping
651
652do
653
654    local a_mathalphabet    = privateattribute("mathalphabet")
655    local a_mathgreek       = privateattribute("mathgreek")
656
657    local relocate          = { }
658
659    local remapalphabets    = mathematics.remapalphabets
660    local fallbackstyleattr = mathematics.fallbackstyleattr
661    local setnodecolor      = colortracers.set
662
663    local function report_remap(tag,id,old,new,extra)
664        if new then
665            report_remapping("remapping %s in font (%s,%s) from %C to %C%s",
666                tag,id,fontdata[id].properties.fontname or "",old,new,extra)
667        else
668            -- error !
669            report_remapping("remapping %s in font (%s,%s) from %C to ?",
670                tag,id,fontdata[id].properties.fontname or "",old)
671        end
672    end
673
674    local function checked(pointer)
675        local char, font = getcharspec(pointer)
676        local data = fontcharacters[font]
677        if not data[char] then
678            local specials = characters.data[char].specials
679            if specials and (specials[1] == "char" or specials[1] == "font") then
680                local newchar = specials[#specials]
681                if trace_remapping then
682                    report_remap("fallback",font,char,newchar)
683                end
684                if trace_analyzing then
685                    setnodecolor(pointer,"font:isol")
686                end
687                setattr(pointer,a_exportstatus,char) -- testcase: exponentiale
688                setchar(pointer,newchar)
689                return true
690            end
691        end
692    end
693
694    -- We can optimize this if we really think that math is a bottleneck which it never
695    -- really is. Beware: the font is the text font in the family, so we only check the
696    -- text font here.
697
698    relocate[mathchar_code] = function(pointer)
699        local g          = getattr(pointer,a_mathgreek) or 0
700        local a          = getattr(pointer,a_mathalphabet) or 0
701     -- local g, a       = getattrs(pointer,a_mathgreek,a_mathalphabet)
702     -- if not a then a = 0 end
703     -- if not g then g = 0 end
704        local char, font, fam = getcharspec(pointer)
705        local characters = fontcharacters[font]
706        if a > 0 or g > 0 then
707            if a > 0 then
708                -- not really critital but we could use properties
709                setattr(pointer,a_mathgreek,0)
710            end
711            if g > 0 then
712                -- not really critital but we could use properties
713                setattr(pointer,a_mathalphabet,0)
714            end
715            local newchar = remapalphabets(char,a,g)
716            if newchar then
717                local newchardata = characters[newchar]
718                if newchardata then
719                    if trace_remapping then
720                        report_remap("char",font,char,newchar,newchardata.commands and " (virtual)" or "")
721                    end
722                    if trace_analyzing then
723                        setnodecolor(pointer,"font:isol")
724                    end
725                    setchar(pointer,newchar)
726                    return true
727                else
728                    local fallback = fallbackstyleattr(a)
729                    if fallback then
730                        local newchar = remapalphabets(char,fallback,g)
731                        if newchar then
732                            if characters[newchar] then
733                                if trace_remapping then
734                                    report_remap("char",font,char,newchar," (fallback remapping used)")
735                                end
736                                if trace_analyzing then
737                                    setnodecolor(pointer,"font:isol")
738                                end
739                                setchar(pointer,newchar)
740                                return true
741                            elseif trace_remapping then
742                                report_remap("char",font,char,newchar," fails (no fallback character)")
743                            end
744                        elseif trace_remapping then
745                            report_remap("char",font,char,newchar," fails (no fallback remap character)")
746                        end
747                    elseif trace_remapping then
748                        report_remap("char",font,char,newchar," fails (no fallback style)")
749                    end
750                end
751            elseif trace_remapping then
752                local chardata = characters[char]
753                if chardata and chardata.commands then
754                    report_remap("char",font,char,char," (virtual)")
755                end
756            end
757        end
758        if not characters[char] then
759            local fnt = getfontoffamily(fam,1)
760            setchar(pointer,errorchar(font,char))
761            if font ~= fnt then
762                errorchar(fnt,char)
763                errorchar(getfontoffamily(fam,2),char)
764            end
765        end
766        if trace_analyzing then
767            setnodecolor(pointer,"font:medi")
768        end
769        if check_coverage then
770            return checked(pointer)
771        end
772    end
773
774    relocate[mathtextchar_code] = function(pointer)
775        if trace_analyzing then
776            setnodecolor(pointer,"font:init")
777        end
778    end
779
780    relocate[delimiter_code] = function(pointer)
781        if trace_analyzing then
782            setnodecolor(pointer,"font:fina")
783        end
784    end
785
786    function handlers.relocate(head,style,penalties)
787        processnoads(head,relocate,"relocate")
788        return true -- not needed
789    end
790
791end
792
793-- rendering (beware, not exported)
794
795do
796
797    local render     = { }
798
799    local rendersets = mathematics.renderings.numbers or { } -- store
800
801    render[mathchar_code] = function(pointer)
802        local attr = getattr(pointer,a_mathrendering)
803        if attr and attr > 0 then
804            local char, font = getcharspec(pointer)
805            local renderset = rendersets[attr]
806            if renderset then
807                local newchar = renderset[char]
808                if newchar then
809                    local characters = fontcharacters[font]
810                    if characters and characters[newchar] then
811                        setchar(pointer,newchar)
812                        setattr(pointer,a_exportstatus,char)
813                    end
814                end
815            end
816        end
817    end
818
819    function handlers.render(head,style,penalties)
820        processnoads(head,render,"render")
821        return true -- not needed
822    end
823
824end
825
826-- some resize options (this works ok because the content is
827-- empty and no larger next will be forced)
828--
829-- beware: we don't use \delcode but \Udelcode and as such have
830-- no large_fam; also, we need to check for subtype and/or
831-- small_fam not being 0 because \. sits in 0,0 by default
832--
833-- todo: just replace the character by an ord noad
834-- and remove the right delimiter as well
835
836do
837
838    local a_mathsize  = privateattribute("mathsize") -- this might move into other fence code
839    local resize      = { }
840
841    resize[fence_code] = function(pointer)
842        local subtype = getsubtype(pointer)
843        if subtype == leftfence_code or subtype == rightfence_code then
844            local a = getattr(pointer,a_mathsize)
845            if a and a > 0 then
846                local method = div(a,100)
847                local size   = a % 100
848                setattr(pointer,a_mathsize,0)
849                local delimiter = getfield(pointer,"delimiter")
850                local chr, fnt, fam = getcharspec(delimiter)
851                if chr > 0 and fnt > 0 then
852                    local data = fontdata[fnt]
853                    local char = mathematics.big(data,chr,size,method)
854                    local ht   = getheight(pointer)
855                    local dp   = getdepth(pointer)
856                    if ht == 1 or dp == 1 then -- 1 scaled point is a signal
857                        local chardata = data.characters[char]
858                        if ht == 1 then
859                            setheight(pointer,chardata.height)
860                        end
861                        if dp == 1 then
862                            setdepth(pointer,chardata.depth)
863                        end
864                    end
865                    if trace_fences then
866                        report_fences("replacing %C by %C using method %a and size %a",chr,char,method,size)
867                    end
868                    setchar(delimiter,char)
869                end
870            end
871        end
872    end
873
874    function handlers.resize(head,style,penalties)
875        processnoads(head,resize,"resize")
876        return true -- not needed
877    end
878
879end
880
881-- still not perfect:
882
883do
884
885    local a_autofence     = privateattribute("mathautofence")
886    local autofences      = { }
887    local dummyfencechar  = 0x2E
888
889    local function makefence(what,char)
890        local d = new_delimiter() -- todo: attr
891        local f = new_fence()     -- todo: attr
892        if char then
893            local sym = getnucleus(char)
894            local chr, fnt, fam = getcharspec(sym)
895            if chr == dummyfencechar then
896                chr = 0
897            end
898            setchar(d,chr)
899            setfam(d,fam)
900            flushnode(sym)
901        end
902        setattrlist(d,char)
903        setattrlist(f,char)
904        setsubtype(f,what)
905        setfield(f,"delimiter",d)
906        setfield(f,"class",-1) -- tex itself does this, so not fenceclasses[what]
907        return f
908    end
909
910    local function show(where,pointer)
911        print("")
912        local i = 0
913        for n in nuts.traverse(pointer) do
914            i = i + 1
915            print(i,where,nuts.tonode(n))
916        end
917        print("")
918    end
919
920    local function makelist(middle,noad,f_o,o_next,c_prev,f_c)
921-- report_fences(
922--     "middle %s, noad %s, open %s, opennext %s, closeprev %s, close %s",
923--     middle or "?",
924--     noad   or "?",
925--     f_o    or "?",
926--     o_next or "?",
927--     c_prev or "?",
928--     f_c    or "?"
929-- )
930        local list = new_submlist()
931        setsubtype(noad,innernoad_code)
932        setnucleus(noad,list)
933        setlist(list,f_o)
934        setlink(f_o,o_next) -- prev of list is nil
935        setlink(c_prev,f_c) -- next of list is nil
936-- show("list",f_o)
937        if middle and next(middle) then
938            local prev    = f_o
939            local current = o_next
940            while current ~= f_c do
941                local midl = middle[current]
942                local next = getnext(current)
943                if midl then
944                    local fence = makefence(middlefence_code,current)
945                    setnucleus(current)
946                    flushnode(current)
947                    middle[current] = nil
948                    -- replace_node
949                    setlink(prev,fence,next)
950                    prev = fence
951                else
952                    prev = current
953                end
954                current = next
955            end
956        end
957        return noad
958    end
959
960    -- relinking is now somewhat overdone
961
962    local function convert_both(open,close,middle)
963        local o_next = getnext(open)
964        if o_next == close then
965            return close
966        else
967            local c_prev, c_next = getboth(close)
968            local f_o = makefence(leftfence_code,open)
969            local f_c = makefence(rightfence_code,close)
970            makelist(middle,open,f_o,o_next,c_prev,f_c)
971            setnucleus(close)
972            flushnode(close)
973            -- open is now a list
974            setlink(open,c_next)
975            return open
976        end
977    end
978
979    local function convert_open(open,last,middle) -- last is really last (final case)
980        local f_o = makefence(leftfence_code,open)
981        local f_c = makefence(rightfence_code)
982        local o_next = getnext(open)
983        makelist(middle,open,f_o,o_next,last,nil)
984        -- open is now a list
985        setlink(open,l_next)
986        return open
987    end
988
989    local function convert_close(first,close,middle)
990        local f_o = makefence(leftfence_code)
991        local f_c = makefence(rightfence_code,close)
992        local c_prev = getprev(close)
993        local f_next = getnext(first)
994        makelist(middle, close, f_o,f_next,c_prev,f_c)
995        -- close is now a list
996        if c_prev ~= first then
997            setlink(first,close)
998        end
999        return close
1000    end
1001
1002    local stacks = setmetatableindex("table")
1003
1004    -- 1=open 2=close 3=middle 4=both
1005
1006    local function processfences(pointer,n,parent)
1007        local current = pointer
1008        local last    = pointer
1009        local start   = pointer
1010        local done    = false
1011        local initial = pointer
1012        local stack   = nil
1013        local middle  = nil -- todo: use properties
1014        while current do
1015-- show("before",pointer)
1016            local id = getid(current)
1017            if id == noad_code then
1018                local a = getattr(current,a_autofence)
1019                if a and a > 0 then
1020                    local stack = stacks[n]
1021                    setattr(current,a_autofence,0) -- hm, better use a property
1022                    local level = #stack
1023                    if a == 1 then
1024                        if trace_fences then
1025                            report_fences("%2i: level %i, handling %s, action %s",n,level,"open","open")
1026                        end
1027                        insert(stack,current)
1028                    elseif a == 2 then
1029                        local open = remove(stack)
1030                        if open then
1031                            if trace_fences then
1032                                report_fences("%2i: level %i, handling %s, action %s",n,level,"close","both")
1033                            end
1034                            current = convert_both(open,current,middle)
1035                        elseif current == start then
1036                            if trace_fences then
1037                                report_fences("%2i: level %i, handling %s, action %s",n,level,"close","skip")
1038                            end
1039                        else
1040                            if trace_fences then
1041                                report_fences("%2i: level %i, handling %s, action %s",n,level,"close","close")
1042                            end
1043                            current = convert_close(initial,current,middle)
1044                            if not parent then
1045                                initial = current
1046                            end
1047                        end
1048                    elseif a == 3 then
1049                        if trace_fences then
1050                            report_fences("%2i: level %i, handling %s, action %s",n,level,"middle","middle")
1051                        end
1052                        if middle then
1053                            middle[current] = last
1054                        else
1055                            middle = { [current] = last }
1056                        end
1057                    elseif a == 4 then
1058                        if not stack or #stack == 0 then
1059                            if trace_fences then
1060                                report_fences("%2i: level %i, handling %s, action %s",n,level,"both","open")
1061                            end
1062                            insert(stack,current)
1063                        else
1064                            local open = remove(stack)
1065                            if open then
1066                                if trace_fences then
1067                                    report_fences("%2i: level %i, handling %s, action %s",n,level,"both","both")
1068                                end
1069                                current = convert_both(open,current,middle)
1070                            elseif current == start then
1071                                if trace_fences then
1072                                    report_fences("%2i: level %i, handling %s, action %s",n,level,"both","skip")
1073                                end
1074                            else
1075                                if trace_fences then
1076                                    report_fences("%2i: level %i, handling %s, action %s",n,level,"both","close")
1077                                end
1078                                current = convert_close(initial,current,middle)
1079                                if not parent then
1080                                    initial = current
1081                                end
1082                            end
1083                        end
1084                    end
1085                    done = true
1086                else
1087                    processstep(current,processfences,n+1,id)
1088                end
1089            else
1090                -- next at current level
1091                processstep(current,processfences,n,id)
1092            end
1093-- show("after",pointer)
1094            last    = current
1095            current = getnext(current)
1096        end
1097        if done then
1098            local stack = stacks[n]
1099            local s = #stack
1100            if s > 0 then
1101                for i=1,s do
1102                    local open = remove(stack)
1103                    if trace_fences then
1104                        report_fences("%2i: level %i, handling %s, action %s",n,#stack,"flush","open")
1105                    end
1106                    last = convert_open(open,last,middle)
1107                end
1108-- show("done",pointer)
1109            end
1110        end
1111    end
1112
1113    -- we can have a first changed node .. an option is to have a leading dummy node in math
1114    -- lists like the par node as it can save a  lot of mess
1115
1116    local enabled = false
1117
1118    implement {
1119        name     = "enableautofences",
1120        onlyonce = true,
1121        actions  = function()
1122            enableaction("math","noads.handlers.autofences")
1123            enabled = true
1124        end
1125    }
1126
1127    function handlers.autofences(head,style,penalties)
1128        if enabled then -- tex.modes.c_math_fences_auto
1129         -- inspect(nodes.totree(head))
1130            processfences(head,1)
1131         -- inspect(nodes.totree(head))
1132        end
1133    end
1134
1135end
1136
1137-- normalize scripts
1138
1139do
1140
1141    local unscript     = { }  noads.processors.unscript = unscript
1142    local superscripts = characters.superscripts
1143    local subscripts   = characters.subscripts
1144    local fractions    = characters.fractions
1145    local replaced     = { }
1146
1147    local function replace(pointer,what,n,parent)
1148        pointer = parent -- we're following the parent list (chars trigger this)
1149        local next = getnext(pointer)
1150        local start_super, stop_super, start_sub, stop_sub
1151        local mode = "unset"
1152        while next and getid(next) == noad_code do
1153            local nextnucleus = getnucleus(next)
1154            if nextnucleus and getid(nextnucleus) == mathchar_code and not getsub(next) and not getsup(next) then
1155                local char = getchar(nextnucleus)
1156                local s = superscripts[char]
1157                if s then
1158                    if not start_super then
1159                        start_super = next
1160                        mode = "super"
1161                    elseif mode == "sub" then
1162                        break
1163                    end
1164                    stop_super = next
1165                    next = getnext(next)
1166                    setchar(nextnucleus,s)
1167                    replaced[char] = (replaced[char] or 0) + 1
1168                    if trace_normalizing then
1169                        report_normalizing("superscript %C becomes %C",char,s)
1170                    end
1171                else
1172                    local s = subscripts[char]
1173                    if s then
1174                        if not start_sub then
1175                            start_sub = next
1176                            mode = "sub"
1177                        elseif mode == "super" then
1178                            break
1179                        end
1180                        stop_sub = next
1181                        next = getnext(next)
1182                        setchar(nextnucleus,s)
1183                        replaced[char] = (replaced[char] or 0) + 1
1184                        if trace_normalizing then
1185                            report_normalizing("subscript %C becomes %C",char,s)
1186                        end
1187                    else
1188                        break
1189                    end
1190                end
1191            else
1192                break
1193            end
1194        end
1195        if start_super then
1196            if start_super == stop_super then
1197                setsup(pointer,getnucleus(start_super))
1198            else
1199                local list = new_submlist() -- todo attr
1200                setlist(list,start_super)
1201                setsup(pointer,list)
1202            end
1203            if mode == "super" then
1204                setnext(pointer,getnext(stop_super))
1205            end
1206            setnext(stop_super)
1207        end
1208        if start_sub then
1209
1210--             if mode == "sub" then
1211--                 local sup = getsup(pointer)
1212--                 if sup and not getsub(pointer) then
1213--                     local nxt = getnext(pointer)
1214--                     local new = new_noad(pointer)
1215--                     setnucleus(new,new_submlist())
1216--                     setlink(pointer,new,nxt)
1217--                     pointer = new
1218--                 end
1219--             end
1220
1221            if start_sub == stop_sub then
1222                setsub(pointer,getnucleus(start_sub))
1223            else
1224                local list = new_submlist() -- todo attr
1225                setlist(list,start_sub)
1226                setsub(pointer,list)
1227            end
1228            if mode == "sub" then
1229                setnext(pointer,getnext(stop_sub))
1230            end
1231            setnext(stop_sub)
1232        end
1233        -- we could return stop
1234    end
1235
1236    unscript[mathchar_code] = replace -- not noads as we need to recurse
1237
1238    function handlers.unscript(head,style,penalties)
1239        processnoads(head,unscript,"unscript")
1240        return true -- not needed
1241    end
1242
1243end
1244
1245do
1246
1247    local unstack   = { }    noads.processors.unstack = unstack
1248    local enabled   = false
1249    local a_unstack = privateattribute("mathunstack")
1250
1251    unstack[noad_code] = function(pointer)
1252        if getattr(pointer,a_unstack) then
1253            local sup = getsup(pointer)
1254            local sub = getsub(pointer)
1255            if sup and sub then
1256             -- if trace_unstacking then
1257             --     report_unstacking() -- todo ... what to show ...
1258             -- end
1259                local nxt = getnext(pointer)
1260                local new = new_noad(pointer)
1261                setnucleus(new,new_submlist())
1262                setsub(pointer)
1263                setsub(new,sub)
1264                setlink(pointer,new,nxt)
1265            end
1266        end
1267    end
1268
1269    function handlers.unstack(head,style,penalties)
1270        if enabled then
1271            processnoads(head,unstack,"unstack")
1272            return true -- not needed
1273        end
1274    end
1275
1276    implement {
1277        name     = "enablescriptunstacking",
1278        onlyonce = true,
1279        actions  = function()
1280            enableaction("math","noads.handlers.unstack")
1281            enabled = true
1282        end
1283    }
1284
1285end
1286
1287do
1288
1289    local function collected(list)
1290        if list and next(list) then
1291            local n, t = 0, { }
1292            for k, v in sortedhash(list) do
1293                n = n + 1
1294                t[n] = formatters["%C"](k)
1295            end
1296            return formatters["% t (n=%s)"](t,n)
1297        end
1298    end
1299
1300    statistics.register("math script replacements", function()
1301        return collected(replaced)
1302    end)
1303
1304    statistics.register("unknown math characters", function()
1305        return collected(unknowns)
1306    end)
1307
1308end
1309
1310-- math alternates: (in xits     lgf: $ABC$ $\cal ABC$ $\mathalternate{cal}\cal ABC$)
1311-- math alternates: (in lucidaot lgf: $ABC \mathalternate{italic} ABC$)
1312
1313-- todo: set alternate for specific symbols
1314-- todo: no need to do this when already loaded
1315-- todo: use a fonts.hashes.mathalternates
1316
1317do
1318
1319    local last = 0
1320
1321    local known = setmetatableindex(function(t,k)
1322        local v = 0 | 2^last
1323        t[k] = v
1324        last = last + 1
1325        return v
1326    end)
1327
1328    local defaults = {
1329        dotless = { feature = 'dtls', value = 1, comment = "Mathematical Dotless Forms" },
1330     -- zero    = { feature = 'zero', value = 1, comment = "Slashed or Dotted Zero" }, -- in no math font (yet)
1331    }
1332
1333    local function initializemathalternates(tfmdata)
1334        local goodies  = tfmdata.goodies
1335        local autolist = defaults -- table.copy(defaults)
1336
1337        local function setthem(newalternates)
1338            local resources      = tfmdata.resources -- was tfmdata.shared
1339            local mathalternates = resources.mathalternates
1340            local alternates, attributes, registered, presets
1341            if mathalternates then
1342                alternates = mathalternates.alternates
1343                attributes = mathalternates.attributes
1344                registered = mathalternates.registered
1345            else
1346                alternates, attributes, registered = { }, { }, { }
1347                mathalternates = {
1348                    attributes = attributes,
1349                    alternates = alternates,
1350                    registered = registered,
1351                    presets    = { },
1352                    resets     = { },
1353                    hashes     = setmetatableindex("table")
1354                }
1355                resources.mathalternates = mathalternates
1356            end
1357            --
1358            for name, data in sortedhash(newalternates) do
1359                if alternates[name] then
1360                    -- ignore
1361                else
1362                    local attr = known[name]
1363                    attributes[attr] = data
1364                    alternates[name] = attr
1365                    registered[#registered+1] = attr
1366                end
1367            end
1368        end
1369
1370        if goodies then
1371            local done = { }
1372            for i=1,#goodies do
1373                -- first one counts
1374                -- we can consider sharing the attributes ... todo (only once scan)
1375                local mathgoodies = goodies[i].mathematics
1376                local alternates  = mathgoodies and mathgoodies.alternates
1377                if alternates then
1378                    if trace_goodies then
1379                        report_goodies("loading alternates for font %a",tfmdata.properties.name)
1380                    end
1381                    for k, v in next, autolist do
1382                        if not alternates[k] then
1383                            alternates[k] = v
1384                        end
1385                    end
1386                    setthem(alternates)
1387                    return
1388                end
1389            end
1390        end
1391
1392        if trace_goodies then
1393            report_goodies("loading default alternates for font %a",tfmdata.properties.name)
1394        end
1395        setthem(autolist)
1396
1397    end
1398
1399    registerotffeature {
1400        name        = "mathalternates",
1401        description = "additional math alternative shapes",
1402        initializers = {
1403            base = initializemathalternates,
1404            node = initializemathalternates,
1405        }
1406    }
1407
1408    -- local getalternate = otf.getalternate (runtime new method so ...)
1409
1410    -- todo: not shared but copies ... one never knows
1411
1412    local a_mathalternate = privateattribute("mathalternate")
1413    local alternate       = { } -- processors.alternate = alternate
1414    local fontdata        = fonts.hashes.identifiers
1415    local fontresources   = fonts.hashes.resources
1416
1417    local function getalternate(fam,tag,current)
1418        -- fam is always zero, so we assume a very consistent setup
1419        local resources = fontresources[getfontoffamily(fam)]
1420        local attribute = unsetvalue
1421        if resources then
1422            local mathalternates = resources.mathalternates
1423            if mathalternates then
1424                local presets = mathalternates.presets
1425                if presets then
1426                    local resets = mathalternates.resets
1427                    attribute = presets[tag]
1428                    if not attribute then
1429                        attribute = 0
1430                        local alternates = mathalternates.alternates
1431                        for s in gmatch(tag,"[^, ]+") do
1432                            if s == v_reset then
1433                                resets[tag] = true
1434                                current = unsetvalue
1435                            else
1436                                local a = alternates[s] -- or known[s]
1437                                if a then
1438                                    attribute = attribute | a
1439                                end
1440                            end
1441                        end
1442                        if attribute == 0 then
1443                            attribute = unsetvalue
1444                        end
1445                        presets[tag] = attribute
1446                    elseif resets[tag] then
1447                        current = unsetvalue
1448                    end
1449                end
1450            end
1451        end
1452        if attribute > 0 and current and current > 0 then
1453            return current | attribute
1454        else
1455            return attribute
1456        end
1457    end
1458
1459    implement {
1460        name      = "presetmathfontalternate",
1461        arguments = "argument",
1462        public    = true,
1463        protected = true,
1464        actions   = function(tag)
1465            if texgetmode() == mathmode_code then
1466                texsetattribute(a_mathalternate,getalternate(0,tag))
1467            end
1468        end,
1469    }
1470
1471    implement {
1472        name      = "setmathfontalternate",
1473        arguments = "argument",
1474        public    = true,
1475        protected = true,
1476        actions   = function(tag)
1477            if texgetmode() == mathmode_code then
1478                texsetattribute(a_mathalternate,getalternate(0,tag,texgetattribute(a_mathalternate)))
1479            end
1480        end,
1481    }
1482
1483    alternate[mathchar_code] = function(pointer) -- slow
1484        local a = getattr(pointer,a_mathalternate)
1485        if a and a > 0 then
1486            setattr(pointer,a_mathalternate,0)
1487            local fontid    = getfont(pointer)
1488            local resources = fontresources[fontid]
1489            if resources then
1490                local mathalternates = resources.mathalternates
1491                if mathalternates then
1492                    local attributes = mathalternates.attributes
1493                    local registered = mathalternates.registered
1494                    local hashes     = mathalternates.hashes
1495                    for i=1,#registered do
1496                        local r = registered[i]
1497                        if (a & r) ~= 0 then
1498                            local char = getchar(pointer)
1499                            local alt  = hashes[i][char]
1500                            if alt == nil then
1501                                local what = attributes[r]
1502                                alt = otf.getalternate(fontdata[fontid],char,what.feature,what.value) or false
1503                                if alt == char then
1504                                    alt = false
1505                                end
1506                                hashes[i][char] = alt
1507                            end
1508                            if alt then
1509                                if trace_alternates then
1510                                    local what = attributes[r]
1511                                    report_alternates("alternate %a, value %a, replacing glyph %U by glyph %U",
1512                                        tostring(what.feature),tostring(what.value),char,alt)
1513                                end
1514                                setchar(pointer,alt)
1515                                break
1516                            end
1517                        end
1518                    end
1519                end
1520            end
1521        end
1522    end
1523
1524    function handlers.alternates(head,style,penalties)
1525        processnoads(head,alternate,"alternate")
1526        return true -- not needed
1527    end
1528
1529end
1530
1531-- italics: we assume that only characters matter
1532--
1533-- = we check for correction first because accessing nodes is slower
1534-- = the actual glyph is not that important (we can control it with numbers)
1535
1536-- Italic correction in luatex math is (was) a mess. There are all kind of assumptions based on
1537-- old fonts and new fonts. Eventually there should be a flag that can signal to ignore all
1538-- those heuristics. We want to deal with it ourselves also in the perspective of mixed math
1539-- and text. Also, for a while in context we had to deal with a mix of virtual math fonts and
1540-- real ones.
1541
1542-- in opentype the italic correction of a limop is added to the width and luatex does
1543-- some juggling that we want to avoid but we need to do something here (in fact, we could
1544-- better fix the width of the character)
1545
1546do
1547
1548    local a_mathitalics = privateattribute("mathitalics")
1549
1550    local italics        = { }
1551    local default_factor = 1/20
1552
1553    local setcolor     = colortracers.set
1554    local resetcolor   = colortracers.reset
1555    local italic_kern  = new_kern
1556
1557    local c_positive_d = "trace:dg"
1558    local c_negative_d = "trace:dr"
1559
1560    local function insert_kern(current,kern)
1561        local sub  = new_submlist() -- todo: attr
1562        local noad = new_noad()     -- todo: attr
1563        setlist(sub,kern)
1564        setnext(kern,noad)
1565        setnucleus(noad,current)
1566        return sub
1567    end
1568
1569    registertracker("math.italics.visualize", function(v)
1570        if v then
1571            italic_kern = function(k)
1572                local n = new_kern(k) -- todo: attr
1573                set_visual(n,"italic")
1574                return n
1575            end
1576        else
1577            italic_kern = new_kern
1578        end
1579    end)
1580
1581    local function getcorrection(method,font,char) -- -- or character.italic -- (this one is for tex)
1582
1583        local visual = chardata[char].visual
1584
1585        if method == 1 then
1586            -- check on state
1587            local italics = fontitalics[font]
1588            if italics then
1589                local character = fontcharacters[font][char]
1590                if character then
1591                    local correction = character.italic
1592                    if correction and correction ~= 0 then
1593                        return correction, visual
1594                    end
1595                end
1596            end
1597        elseif method == 2 then
1598            -- no check
1599            local character = fontcharacters[font][char]
1600            if character then
1601                local correction = character.italic
1602                if correction and correction ~= 0 then
1603                    return correction, visual
1604                end
1605            end
1606        elseif method == 3 then
1607            -- check on visual
1608            if visual == "it" or visual == "bi" then
1609                local character = fontcharacters[font][char]
1610                if character then
1611                    local correction = character.italic
1612                    if correction and correction ~= 0 then
1613                        return correction, visual
1614                    end
1615                end
1616            end
1617        elseif method == 4 then
1618            -- combination of 1 and 3
1619            local italics = fontitalics[font]
1620            if italics and (visual == "it" or visual == "bi") then
1621                local character = fontcharacters[font][char]
1622                if character then
1623                    local correction = character.italic
1624                    if correction and correction ~= 0 then
1625                        return correction, visual
1626                    end
1627                end
1628            end
1629        end
1630
1631    end
1632
1633    italics[mathchar_code] = function(pointer,what,n,parent)
1634        local method = getattr(pointer,a_mathitalics)
1635        if method and method > 0 and method < 100 then
1636            local char, font = getcharspec(pointer)
1637            local correction, visual = getcorrection(method,font,char)
1638            if correction and correction ~= 0 then
1639                local next_noad = getnext(parent)
1640                if not next_noad then
1641                    if n == 1 then
1642                        -- only at the outer level .. will become an option (always,endonly,none)
1643                        if trace_italics then
1644                            report_italics("method %a, flagging italic correction %p between %C and end math",method,correction,char)
1645                        end
1646                        if correction > 0 then
1647                            correction = correction + 100
1648                        else
1649                            correction = correction - 100
1650                        end
1651                        correction = round(correction)
1652                        setattr(pointer,a_mathitalics,correction)
1653                        setattr(parent,a_mathitalics,correction)
1654                        return -- so no reset later on
1655                    end
1656                end
1657            end
1658        end
1659        setattr(pointer,a_mathitalics,unsetvalue)
1660    end
1661
1662    function handlers.italics(head,style,penalties)
1663        processnoads(head,italics,"italics")
1664        return true -- not needed
1665    end
1666
1667    local enable = function()
1668        enableaction("math", "noads.handlers.italics")
1669        if trace_italics then
1670            report_italics("enabling math italics")
1671        end
1672        -- we enable math (unless already enabled elsewhere)
1673        typesetters.italics.enablemath()
1674        enable = false
1675    end
1676
1677    -- best do this only on math mode (less overhead)
1678
1679    function mathematics.setitalics(name)
1680        if enable then
1681            enable()
1682        end
1683        texsetattribute(a_mathitalics,name and name ~= v_reset and tonumber(name) or unsetvalue) -- maybe also v_none
1684    end
1685
1686    function mathematics.getitalics(name)
1687        if enable then
1688            enable()
1689        end
1690        context(name and name ~= v_reset and tonumber(name) or unsetvalue)
1691    end
1692
1693    function mathematics.resetitalics()
1694        texsetattribute(a_mathitalics,unsetvalue)
1695    end
1696
1697    implement {
1698        name      = "initializemathitalics",
1699        actions   = enable,
1700        onlyonce  = true,
1701    }
1702
1703    implement {
1704        name      = "setmathitalics",
1705        actions   = mathematics.setitalics,
1706        arguments = "string",
1707    }
1708
1709    implement {
1710        name      = "getmathitalics",
1711        actions   = mathematics.getitalics,
1712        arguments = "string",
1713    }
1714
1715    implement {
1716        name      = "resetmathitalics",
1717        actions   = mathematics.resetitalics
1718    }
1719
1720end
1721
1722do
1723
1724    -- math kerns (experiment) in goodies:
1725    --
1726    -- mathematics = {
1727    --     kernpairs = {
1728    --         [0x1D44E] = {
1729    --             [0x1D44F] = 400, -- 𝑎𝑏
1730    --         }
1731    --     },
1732    -- }
1733
1734    local a_kernpairs = privateattribute("mathkernpairs")
1735    local kernpairs   = { }
1736
1737    local function enable()
1738        enableaction("math", "noads.handlers.kernpairs")
1739        if trace_kernpairs then
1740            report_kernpairs("enabling math kern pairs")
1741        end
1742        enable = false
1743    end
1744
1745    implement {
1746        name      = "initializemathkernpairs",
1747        actions   = enable,
1748        onlyonce  = true,
1749    }
1750
1751    local hash = setmetatableindex(function(t,font)
1752        local g = fontdata[font].goodies
1753        local m = g and g[1] and g[1].mathematics
1754        local k = m and m.kernpairs
1755        t[font] = k
1756        return k
1757    end)
1758
1759    -- no correction after prime because that moved to a superscript
1760
1761    kernpairs[mathchar_code] = function(pointer,what,n,parent)
1762        if getattr(pointer,a_kernpairs) == 1 then
1763            local first, firstfont = getcharspec(pointer)
1764            local list = hash[firstfont]
1765            if list then
1766                local found = list[first]
1767                if found then
1768                    local next = getnext(parent)
1769                    if next and getid(next) == noad_code then
1770                        pointer = getnucleus(next)
1771                        if pointer then
1772                            local second, secondfont = getcharspec(pointer)
1773                            if secondfont == firstfont then
1774                                local kern = found[second]
1775                                if kern then
1776                                    kern = kern * fontparameters[firstfont].hfactor
1777                                    if trace_kernpairs then
1778                                        report_kernpairs("adding %p kerning between %C and %C",kern,first,second)
1779                                    end
1780                                    setlink(parent,new_kern(kern),getnext(parent)) -- todo: attr
1781                                end
1782                            end
1783                        end
1784                    end
1785                end
1786            end
1787        end
1788    end
1789
1790    function handlers.kernpairs(head,style,penalties)
1791        processnoads(head,kernpairs,"kernpairs")
1792        return true -- not needed
1793    end
1794
1795end
1796
1797-- primes and such
1798
1799do
1800
1801    -- is validpair stil needed?
1802
1803    local a_mathcollapsing = privateattribute("mathcollapsing")
1804    local collapse         = { }
1805    local mathlists        = characters.mathlists
1806    local validpair        = {
1807        [ordnoad_code]     = true,
1808        [opnoad_code]      = true,
1809        [binnoad_code]     = true, -- new
1810        [relnode_code]     = true,
1811        [opennoad_code]    = true, -- new
1812        [closenoad_code]   = true, -- new
1813        [punctnoad_code]   = true, -- new
1814        [innernoad_code]   = false,
1815        [undernoad_code]   = false,
1816        [overnoad_code]    = false,
1817        [vcenternoad_code] = false,
1818    }
1819
1820    local reported = setmetatableindex("table")
1821
1822    collapse[mathchar_code] = function(pointer,what,n,parent)
1823
1824        if parent and mathlists[getchar(pointer)] then
1825            local found, last, lucleus, lsup, lsub, category
1826            local tree    = mathlists
1827            local current = parent
1828            while current and validpair[getsubtype(current)] do
1829                local nucleus = getnucleus(current) -- == pointer
1830                local sub     = getsub(current)
1831                local sup     = getsup(current)
1832                local char    = getchar(nucleus)
1833                if char then
1834                    local match = tree[char]
1835                    if match then
1836                        local method = getattr(current,a_mathcollapsing)
1837                        if method and method > 0 and method <= 3 then
1838                            local specials = match.specials
1839                            local mathlist = match.mathlist
1840                            local ligature
1841                            if method == 1 then
1842                                ligature = specials
1843                            elseif method == 2 then
1844                                ligature = specials or mathlist
1845                            else -- 3
1846                                ligature = mathlist or specials
1847                            end
1848                            if ligature then
1849                                category = mathlist and "mathlist" or "specials"
1850                                found    = ligature
1851                                last     = current
1852                                lucleus  = nucleus
1853                                lsup     = sup
1854                                lsub     = sub
1855                            end
1856                            tree = match
1857                            if sub or sup then
1858                                break
1859                            else
1860                                current = getnext(current)
1861                            end
1862                        else
1863                            break
1864                        end
1865                    else
1866                        break
1867                    end
1868                else
1869                    break
1870                end
1871            end
1872            if found and last and lucleus then
1873                local id         = getfont(lucleus)
1874                local characters = fontcharacters[id]
1875                local replace    = characters and characters[found]
1876                if not replace then
1877                    if not reported[id][found] then
1878                        reported[id][found] = true
1879                        report_collapsing("%s ligature %C from %s","ignoring",found,category)
1880                    end
1881                elseif trace_collapsing then
1882                    report_collapsing("%s ligature %C from %s","creating",found,category)
1883                end
1884                setchar(pointer,found)
1885                local l = getnext(last)
1886                local c = getnext(parent)
1887                if lsub then
1888                    setsub(parent,lsub)
1889                    setsub(last)
1890                end
1891                if lsup then
1892                    setsup(parent,lsup)
1893                    setsup(last)
1894                end
1895                while c ~= l do
1896                    local n = getnext(c)
1897                    flushnode(c)
1898                    c = n
1899                end
1900                setlink(parent,l)
1901            end
1902        end
1903    end
1904
1905    function noads.handlers.collapse(head,style,penalties)
1906        processnoads(head,collapse,"collapse")
1907        return true -- not needed
1908    end
1909
1910    local enable = function()
1911        enableaction("math", "noads.handlers.collapse")
1912        if trace_collapsing then
1913            report_collapsing("enabling math collapsing")
1914        end
1915        enable = false
1916    end
1917
1918    implement {
1919        name      = "initializemathcollapsing",
1920        actions   = enable,
1921        onlyonce  = true,
1922    }
1923
1924end
1925
1926do
1927    -- inner under over vcenter
1928
1929    local fixscripts = { }
1930    local movesub    = {
1931        -- primes
1932        [0x2032] = 0xFE932,
1933        [0x2033] = 0xFE933,
1934        [0x2034] = 0xFE934,
1935        [0x2057] = 0xFE957,
1936        -- reverse primes
1937        [0x2035] = 0xFE935,
1938        [0x2036] = 0xFE936,
1939        [0x2037] = 0xFE937,
1940    }
1941
1942    mathematics.virtualize(movesub)
1943
1944    local nosuperscript_code = tex.mathoptioncodes.nosuperscript
1945
1946    local function fixsupscript(parent,current,current_char,new_char)
1947        if new_char ~= current_char and new_char ~= true then
1948            setchar(current,new_char)
1949            if trace_fixing then
1950                report_fixing("fixing subscript, replacing superscript %U by %U",current_char,new_char)
1951            end
1952        else
1953            if trace_fixing then
1954                report_fixing("fixing subscript, superscript %U",current_char)
1955            end
1956        end
1957        setfield(parent,"options",nosuperscript_code)
1958    end
1959
1960    local function move_none_none(parent,prev,nuc,oldchar,newchar)
1961        fixsupscript(prev,nuc,oldchar,newchar)
1962        local sub = getsub(parent)
1963        setsup(prev,nuc)
1964        setsub(prev,sub)
1965        local dummy = copy_node(nuc)
1966        setchar(dummy,0)
1967        setnucleus(parent,dummy)
1968        setsub(parent)
1969    end
1970
1971    local function move_none_psub(parent,prev,nuc,oldchar,newchar)
1972        fixsupscript(prev,nuc,oldchar,newchar)
1973        setsup(prev,nuc)
1974        local dummy = copy_node(nuc)
1975        setchar(dummy,0)
1976        setnucleus(parent,dummy)
1977    end
1978
1979    fixscripts[mathchar_code] = function(pointer,what,n,parent,nested) -- todo: switch to turn in on and off
1980        if parent then
1981            local oldchar = getchar(pointer)
1982            local newchar = movesub[oldchar]
1983            if newchar then
1984                local nuc = getnucleus(parent)
1985                if pointer == nuc then
1986                    local sub = getsub(pointer)
1987                    local sup = getsup(pointer)
1988                    if sub then
1989                        if sup then
1990                            -- print("[char] sub sup")
1991                        else
1992                            -- print("[char] sub ---")
1993                        end
1994                    elseif sup then
1995                        -- print("[char] --- sup")
1996                    else
1997                        local prev = getprev(parent)
1998                        if prev and getid(prev) == noad_code then
1999                            local psub = getsub(prev)
2000                            local psup = getsup(prev)
2001                            if psub then
2002                                if psup then
2003                                    -- print("sub sup [char] --- ---")
2004                                else
2005                                    -- print("sub --- [char] --- ---")
2006                                    move_none_psub(parent,prev,nuc,oldchar,newchar)
2007                                end
2008                            elseif psup then
2009                                -- print("--- sup [char] --- ---")
2010                            else
2011                                -- print("[char] --- ---")
2012                                move_none_none(parent,prev,nuc,oldchar,newchar)
2013                            end
2014                        else
2015                            -- print("no prev [char]")
2016                        end
2017                    end
2018                else
2019                    -- print("[char]")
2020                end
2021            end
2022        end
2023    end
2024
2025    function noads.handlers.fixscripts(head,style,penalties)
2026        processnoads(head,fixscripts,"fixscripts")
2027        return true -- not needed
2028    end
2029
2030end
2031
2032-- variants
2033
2034do
2035
2036    local variants      = { }
2037    local validvariants = { -- fast check on valid
2038        [0x2229] = 0xFE00, [0x222A] = 0xFE00,
2039        [0x2268] = 0xFE00, [0x2269] = 0xFE00,
2040        [0x2272] = 0xFE00, [0x2273] = 0xFE00,
2041        [0x228A] = 0xFE00, [0x228B] = 0xFE00,
2042        [0x2293] = 0xFE00, [0x2294] = 0xFE00,
2043        [0x2295] = 0xFE00,
2044        [0x2297] = 0xFE00,
2045        [0x229C] = 0xFE00,
2046        [0x22DA] = 0xFE00, [0x22DB] = 0xFE00,
2047        [0x2A3C] = 0xFE00, [0x2A3D] = 0xFE00,
2048        [0x2A9D] = 0xFE00, [0x2A9E] = 0xFE00,
2049        [0x2AAC] = 0xFE00, [0x2AAD] = 0xFE00,
2050        [0x2ACB] = 0xFE00, [0x2ACC] = 0xFE00,
2051    }
2052
2053    variants[mathchar_code] = function(pointer,what,n,parent) -- also set export value
2054        local char = getchar(pointer)
2055        local selector = validvariants[char]
2056        if selector then
2057            local next = getnext(parent)
2058            if next and getid(next) == noad_code then
2059                local nucleus = getnucleus(next)
2060                if nucleus and getid(nucleus) == mathchar_code and getchar(nucleus) == selector then
2061                    local variant
2062                    local tfmdata = fontdata[getfont(pointer)]
2063                    local mathvariants = tfmdata.resources.variants -- and variantdata
2064                    if mathvariants then
2065                        mathvariants = mathvariants[selector]
2066                        if mathvariants then
2067                            variant = mathvariants[char]
2068                        end
2069                    end
2070                    if variant then
2071                        setchar(pointer,variant)
2072                        setattr(pointer,a_exportstatus,char) -- we don't export the variant as it's visual markup
2073                        if trace_variants then
2074                            report_variants("variant (%U,%U) replaced by %U",char,selector,variant)
2075                        end
2076                    else
2077                        if trace_variants then
2078                            report_variants("no variant (%U,%U)",char,selector)
2079                        end
2080                    end
2081                    setprev(next,pointer)
2082                    setnext(parent,getnext(next))
2083                    flushnode(next)
2084                end
2085            end
2086        end
2087    end
2088
2089    function handlers.variants(head,style,penalties)
2090        processnoads(head,variants,"unicode variant")
2091        return true -- not needed
2092    end
2093
2094end
2095
2096-- for manuals
2097
2098do
2099
2100    local classes = { }
2101    local colors  = {
2102        [relnode_code]     = "trace:dr",
2103        [ordnoad_code]     = "trace:db",
2104        [binnoad_code]     = "trace:dg",
2105        [opennoad_code]    = "trace:dm",
2106        [closenoad_code]   = "trace:dm",
2107        [punctnoad_code]   = "trace:dc",
2108     -- [opnoad_code]      = "",
2109     -- [innernoad_code    = "",
2110     -- [undernoad_code]   = "",
2111     -- [overnoad_code]    = "",
2112     -- [vcenternoad_code] = "",
2113    }
2114
2115    local setcolor   = colortracers.set
2116    local resetcolor = colortracers.reset
2117
2118    classes[mathchar_code] = function(pointer,what,n,parent)
2119        local color = colors[getsubtype(parent)]
2120        if color then
2121            setcolor(pointer,color)
2122        else
2123            resetcolor(pointer)
2124        end
2125    end
2126
2127    function handlers.classes(head,style,penalties)
2128        processnoads(head,classes,"classes")
2129        return true -- not needed
2130    end
2131
2132    registertracker("math.classes",function(v)
2133        setaction("math","noads.handlers.classes",v)
2134    end)
2135
2136end
2137
2138-- experimental
2139
2140do
2141
2142 -- mathematics.registerdomain {
2143 --     name       = "foo",
2144 --     parents    = { "bar" },
2145 --     characters = {
2146 --         [0x123] = { char = 0x234, class = binary },
2147 --     },
2148 -- }
2149
2150    local domains       = { }
2151    local categories    = { }
2152    local numbers       = { }
2153    local a_mathdomain  = privateattribute("mathdomain")
2154    mathematics.domains = categories
2155    local permitted     = {
2156        ordinary    = ordnoad_code,
2157        binary      = binnoad_code,
2158        relation    = relnode_code,
2159        punctuation = punctnoad_code,
2160        inner       = innernoad_code,
2161    }
2162
2163    function mathematics.registerdomain(data)
2164        local name = data.name
2165        if not name then
2166            return
2167        end
2168        local attr       = #numbers + 1
2169        categories[name] = data
2170        numbers[attr]    = data
2171        data.attribute   = attr
2172        -- we delay hashing
2173        return attr
2174    end
2175
2176    local enable
2177
2178    enable = function()
2179        enableaction("math", "noads.handlers.domains")
2180        if trace_domains then
2181            report_domains("enabling math domains")
2182        end
2183        enable = false
2184    end
2185
2186    function mathematics.setdomain(name)
2187        if enable then
2188            enable()
2189        end
2190        local data = name and name ~= v_reset and categories[name]
2191        texsetattribute(a_mathdomain,data and data.attribute or unsetvalue)
2192    end
2193
2194    function mathematics.getdomain(name)
2195        if enable then
2196            enable()
2197        end
2198        local data = name and name ~= v_reset and categories[name]
2199        context(data and data.attribute or unsetvalue)
2200    end
2201
2202    implement {
2203        name      = "initializemathdomain",
2204        actions   = enable,
2205        onlyonce  = true,
2206    }
2207
2208    implement {
2209        name      = "setmathdomain",
2210        arguments = "string",
2211        actions   = mathematics.setdomain,
2212    }
2213
2214    implement {
2215        name      = "getmathdomain",
2216        arguments = "string",
2217        actions   = mathematics.getdomain,
2218    }
2219
2220    local function makehash(data)
2221        local hash    = { }
2222        local parents = data.parents
2223        if parents then
2224            local function merge(name)
2225                if name then
2226                    local c = categories[name]
2227                    if c then
2228                        local hash = c.hash
2229                        if not hash then
2230                            hash = makehash(c)
2231                        end
2232                        for k, v in next, hash do
2233                            hash[k] = v
2234                        end
2235                    end
2236                end
2237            end
2238            if type(parents) == "string" then
2239                merge(parents)
2240            elseif type(parents) == "table" then
2241                for i=1,#parents do
2242                    merge(parents[i])
2243                end
2244            end
2245        end
2246        local characters = data.characters
2247        if characters then
2248            for k, v in next, characters do
2249             -- local chr = n.char
2250                local cls = v.class
2251                if cls then
2252                    v.code = permitted[cls]
2253                else
2254                    -- invalid class
2255                end
2256                hash[k] = v
2257            end
2258        end
2259        data.hash = hash
2260        return hash
2261    end
2262
2263    domains[mathchar_code] = function(pointer,what,n,parent)
2264        local attr = getattr(pointer,a_mathdomain)
2265        if attr then
2266            local domain = numbers[attr]
2267            if domain then
2268                local hash = domain.hash
2269                if not hash then
2270                    hash = makehash(domain)
2271                end
2272                local char = getchar(pointer)
2273                local okay = hash[char]
2274                if okay then
2275                    local chr = okay.char
2276                    local cls = okay.code
2277                    if chr and chr ~= char then
2278                        setchar(pointer,chr)
2279                    end
2280                    if cls and cls ~= getsubtype(parent) then
2281                        setsubtype(parent,cls)
2282                    end
2283                end
2284            end
2285        end
2286    end
2287
2288    function handlers.domains(head,style,penalties)
2289        processnoads(head,domains,"domains")
2290        return true -- not needed
2291    end
2292
2293end
2294
2295-- just for me
2296
2297function handlers.showtree(head,style,penalties)
2298    inspect(nodes.totree(tonut(head)))
2299end
2300
2301registertracker("math.showtree",function(v)
2302    setaction("math","noads.handlers.showtree",v)
2303end)
2304
2305-- also for me
2306
2307do
2308
2309    local applyvisuals = nuts.applyvisuals
2310    local visual       = false
2311
2312    function handlers.makeup(head)
2313        applyvisuals(head,visual)
2314    end
2315
2316    registertracker("math.makeup",function(v)
2317        visual = v
2318        setaction("math","noads.handlers.makeup",v)
2319    end)
2320
2321end
2322
2323-- the normal builder
2324
2325-- do
2326--
2327--     local force_penalties = false
2328--
2329--     function builders.kernel.mlisttohlist(head,style,penalties)
2330--         return mlisttohlist(head,style,force_penalties or penalties)
2331--     end
2332--
2333--     implement {
2334--         name      = "setmathpenalties",
2335--         arguments = "integer",
2336--         actions   = function(p)
2337--             force_penalties = p > 0
2338--         end,
2339--     }
2340--
2341-- end
2342
2343builders.kernel.mlisttohlist = mlisttohlist
2344
2345local actions = tasks.actions("math") -- head, style, penalties
2346
2347local starttiming, stoptiming = statistics.starttiming, statistics.stoptiming
2348
2349function processors.mlisttohlist(head,style,penalties)
2350    starttiming(noads)
2351    head = actions(head,style,penalties)
2352    stoptiming(noads)
2353    return head
2354end
2355
2356callbacks.register('mlist_to_hlist',processors.mlisttohlist,"preprocessing math list")
2357
2358-- tracing
2359
2360statistics.register("math processing time", function()
2361    return statistics.elapsedseconds(noads)
2362end)
2363