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