math-ini.lmt /size: 39 Kb    last modification: 2024-01-16 09:02
1if not modules then modules = { } end modules ['math-ini'] = {
2    version   = 1.001,
3    comment   = "companion to math-ini.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9-- The way we do math in \CONTEXT\ \MKIV\ differs from other macro packages so you
10-- should not mix the somewhat unique (and bit weird) approach here with the more
11-- traditional (\TEX) approach. Also, we use char-def.lua as starting point and that
12-- file is quite \CONTEXT\ specific. When we added math to that there was no
13-- interest (quite the contrary) so we didn't went generic there which in retrospect
14-- also gives us the freedom to add more information, something that happens
15-- occasionally. Because that file is shared between \MKIV\ and \LMTX\ some
16-- information is only used by \LMTX. We also have quite some runs over the math
17-- list but that has been so since we started and performance will not degrade much
18-- by it; after all math is not that demanding. More details can be found in the
19-- manuals that discuss math. Most code (and concepts) date from 2005 so maybe I
20-- will upgrade the lot some day, although it has been adapted on the way to the
21-- changes in the engine(s).
22
23local next, type = next, type
24local formatters, find, nospaces = string.formatters, string.find, string.nospaces
25local utfchar, utfbyte = utf.char, utf.byte
26local sortedhash = table.sortedhash
27local toboolean = toboolean
28
29local context               = context
30local implement             = interfaces.implement
31
32local ctx_doifelsesomething = commands.doifelsesomething
33
34local trace_defining        = false  trackers.register("math.defining", function(v) trace_defining = v end)
35
36local report_math           = logs.reporter("mathematics","initializing")
37
38mathematics                 = mathematics or { }
39local mathematics           = mathematics
40
41mathematics.extrabase       = fonts.privateoffsets.mathextrabase -- here we push some virtuals
42mathematics.privatebase     = fonts.privateoffsets.mathbase      -- here we push the ex
43
44local unsetvalue            = attributes.unsetvalue
45local allocate              = utilities.storage.allocate
46local chardata              = characters.data
47
48local texsetattribute       = tex.setattribute
49local setmathcode           = tex.setmathcode
50local setdelcode            = tex.setdelcode
51local texintegerdef         = tex.integerdef
52
53local getfontoffamily       = tex.getfontoffamily
54
55local fontchardata          = fonts.hashes.characters
56
57setdelcode = function() end
58
59-- These are different from mkiv with luatex.
60
61local classes          = allocate { unset = 64 } -- or -1
62local classnames       = allocate { }
63local maxengineclass   = 63
64local lastengineclass  = 0
65local lastprivateclass = maxengineclass
66
67for k, v in next, nodes.noadcodes do
68    if type(k) == "string" then
69        classes[k] = v
70--         local n = classnames[v]
71--         if not n or #k < #n then
72--             classnames[v] = k
73--         end
74    elseif k > lastengineclass then
75        lastengineclass = k
76    end
77end
78
79local ordinary_class    = classes.ordinary
80local operator_class    = classes.operator
81local binary_class      = classes.binary
82local relation_class    = classes.relation
83local open_class        = classes.open
84local close_class       = classes.close
85local punctuation_class = classes.punctuation
86local middle_class      = classes.middle
87local accent_class      = classes.accent
88local radical_class     = classes.radical
89local fraction_class    = classes.fraction
90local under_class       = classes.under
91local over_class        = classes.over
92local fenced_class      = classes.fenced
93local ghost_class       = classes.ghost
94
95-- these will go
96
97classes.ord             = ordinary_class
98classes.op              = operator_class
99classes.bin             = binary_class
100classes.rel             = relation_class
101classes.opening         = open_class         -- will go
102classes.closing         = close_class        -- will go
103classes.punct           = punctuation_class
104classes.frac            = fraction_class
105classes.rad             = radical_class
106classes.fen             = fenced_class
107classes.gst             = ghost_class
108
109-- these will go too
110
111classes.limop           = operator_class
112classes.limoperator     = operator_class
113classes.nolop           = operator_class
114classes.nolimoperator   = operator_class
115classes.large           = operator_class
116classes.largeoperator   = operator_class
117
118-- special in the engine : variable active inner vcenter
119
120local function registerengineclass(name,short)
121    local class = classes[name]
122    if not class then
123        if lastengineclass < maxengineclass then
124            lastengineclass = lastengineclass + 1
125            class = lastengineclass
126            classnames[class] = short or name
127        else
128            class = ordinary_class
129        end
130    else
131        classnames[class] = short or name
132    end
133    classes[class] = name
134    classes[name] = class
135    return class
136end
137
138-- predefined classes
139
140registerengineclass("ordinary",    "ord")
141registerengineclass("operator",    "ope")
142registerengineclass("binary",      "bin")
143registerengineclass("relation",    "rel")
144registerengineclass("open",        "ope")
145registerengineclass("close",       "clo")
146registerengineclass("punctuation", "pun")
147registerengineclass("variable",    "var") -- not used
148registerengineclass("active",      "act") -- not used
149registerengineclass("inner",       "inn") -- not used
150registerengineclass("middle",      "mid")
151registerengineclass("accent",      "acc")
152registerengineclass("radical",     "rad")
153registerengineclass("fraction",    "fra")
154registerengineclass("under",       "und")
155registerengineclass("over",        "ove")
156registerengineclass("fenced",      "fen")
157registerengineclass("ghost",       "gho")
158registerengineclass("vcenter",     "vce") -- not used
159
160-- additional classes
161
162registerengineclass("explicit",        "xpl")
163registerengineclass("imaginary",       "img")
164registerengineclass("differential",    "dif")
165registerengineclass("exponential",     "exp")
166registerengineclass("integral",        "int")
167registerengineclass("ellipsis",        "ell")
168registerengineclass("function",        "fnc")
169registerengineclass("digit",           "dig")  local division_class =
170registerengineclass("division",        "div")
171registerengineclass("factorial",       "fac")
172registerengineclass("wrapped",         "wra")
173registerengineclass("construct",       "con")
174registerengineclass("dimension",       "dim")
175registerengineclass("unary",           "una")
176registerengineclass("textpunctuation", "tpu")
177registerengineclass("unspaced",        "uns")
178registerengineclass("experimental",    "exp")
179registerengineclass("fake",            "fak")
180registerengineclass("numbergroup",     "ngr")
181
182registerengineclass("maybeordinary",   "mor")
183registerengineclass("mayberelation",   "mre")
184registerengineclass("maybebinary",     "mbi")
185
186registerengineclass("chemicalbond",    "chb")
187registerengineclass("implication",     "imp")
188
189local specialclasses = tex.specialmathclasscodes
190
191classes["all"]   = specialclasses["all"]    classnames[specialclasses["all"]  ] = "all"
192classes["begin"] = specialclasses["begin"]  classnames[specialclasses["begin"]] = "beg"
193classes["end"]   = specialclasses["end"]    classnames[specialclasses["end"]  ] = "end"
194
195callbacks.register("get_noad_class", function(n) return classnames[n] end,"provide math class name")
196
197local function registerprivateclass(name,parent)
198    local class = parent and classes[parent] or classes[name]
199    if not class then
200        lastprivateclass = lastprivateclass + 1
201        class = lastprivateclass
202        classes[name] = class
203        -- also setup
204    end
205    return class
206end
207
208local function toengineclass(class)
209    if type(class) == "string" then
210        return classes[class] or ordinary_class
211    elseif class > lastengineclass then
212        return ordinary_class
213    else
214        return class
215    end
216end
217
218implement {
219    name      = "registerengineclass",
220    public    = true,
221    protected = true,
222    arguments = { "optional", "optional" },
223    actions   = registerengineclass,
224}
225
226local topaccent_class    = registerprivateclass("topaccent")
227local bottomaccent_class = registerprivateclass("bottomaccent")
228local delimiter_class    = registerprivateclass("delimiter")
229local root_class         = registerprivateclass("root")
230local prime_class        = registerprivateclass("prime")
231
232registerprivateclass("botaccent","bottomaccent")
233
234local accents = allocate {
235    accent       = true, -- some can be both
236    topaccent    = true,  [topaccent_class]    = true,
237    bottomaccent = true,  [bottomaccent_class] = true,
238    botaccent    = true,
239    under        = true,  [under_class]        = true,
240    over         = true,  [over_class]         = true,
241    unknown      = false,
242}
243
244local integer_value = tokens.values.integer
245
246implement {
247    name      = "mathclassvalue",
248 -- usage     = "value",
249    public    = true,
250    arguments = "string",
251    actions = function(name)
252     -- return integer_value, classes[name] or ordinary_class
253        context(tostring(classes[name] or ordinary_class))
254    end
255}
256
257-- used in math-tag: so there we need to make things ord etc to fit within
258-- mathml
259
260local codes = allocate {
261    ordinary       = ordinary_class,    [ordinary_class]    = "ordinary",
262    largeoperator  = operator_class,    [operator_class]    = "largeoperator",
263    binaryoperator = binary_class,      [binary_class]      = "binaryoperator",
264    relation       = relation_class,    [relation_class]    = "relation",
265    openingsymbol  = open_class,        [open_class]        = "openingsymbol",
266    closingsymbol  = close_class,       [close_class]       = "closingsymbol",
267    punctuation    = punctuation_class, [punctuation_class] = "punctuation",
268    middlesymbol   = middle_class,      [middle_class]      = "middlesymbol",
269}
270
271local extensibles = allocate {
272           unknown    = 0,
273    l = 1, left       = 1,
274    r = 2, right      = 2,
275    h = 3, horizontal = 3,-- lr or rl
276    u = 5, up         = 4,
277    d = 5, down       = 5,
278    v = 6, vertical   = 6,-- ud or du
279    m = 7, mixed      = 7,
280}
281
282table.setmetatableindex(extensibles,function(t,k) t[k] = 0 return 0 end)
283
284local virtualized = allocate {
285}
286
287function mathematics.virtualize(unicode,virtual)
288
289    local function virtualize(k,v)
290        local c = virtualized[k]
291        if c == v then
292            report_math("character %C is already virtualized to %C",k,v)
293        elseif c then
294            report_math("character %C is already virtualized to %C, ignoring mapping to %C",k,c,v)
295        else
296            virtualized[k] = v
297        end
298    end
299
300    if type(unicode) == "table" then
301        for k, v in next, unicode do
302            virtualize(k,v)
303        end
304    elseif type(unicode) == "number" and type(virtual) == "number" then
305        virtualize(unicode,virtual)
306 -- else
307        -- error
308    end
309end
310
311mathematics.extensibles   = extensibles
312mathematics.classes       = classes
313mathematics.toengineclass = toengineclass
314mathematics.classnames    = classnames
315mathematics.codes         = codes
316-----------.accents       = codes
317mathematics.virtualized   = virtualized
318
319-- This is relatively new and experimental:
320
321do
322
323    local dictionaries       = mathematics.dictionaries or { }
324    mathematics.dictionaries = dictionaries
325
326    local names    = dictionaries.names    or utilities.storage.allocate()
327    local groups   = dictionaries.groups   or utilities.storage.allocate()
328    local data     = dictionaries.data     or utilities.storage.allocate()
329    local sets     = dictionaries.sets     or utilities.storage.allocate()
330    local variants = dictionaries.variants or utilities.storage.allocate() -- todo: get from char-def
331    local defaults = dictionaries.defaults or utilities.storage.allocate() -- todo: get from char-def
332
333    storage.register("mathematics/dictionaries/names",    names,    "mathematics.dictionaries.names")
334    storage.register("mathematics/dictionaries/groups",   groups,   "mathematics.dictionaries.groups")
335    storage.register("mathematics/dictionaries/data",     data,     "mathematics.dictionaries.data")
336    storage.register("mathematics/dictionaries/sets",     sets,     "mathematics.dictionaries.sets")
337    storage.register("mathematics/dictionaries/variants", variants, "mathematics.dictionaries.variants")
338    storage.register("mathematics/dictionaries/defaults", defaults, "mathematics.dictionaries.defaults")
339
340    dictionaries.names    = dictionaries.names    or names
341    dictionaries.groups   = dictionaries.groups   or groups
342    dictionaries.data     = dictionaries.data     or data
343    dictionaries.sets     = dictionaries.sets     or sets
344    dictionaries.variants = dictionaries.variants or variants
345    dictionaries.defaults = dictionaries.defaults or defaults
346
347    if not sets.n then
348        sets.n = 0
349    end
350
351    function dictionaries.registergroup(name)
352        local group = rawget(names,name)
353        if not group then
354            group = #groups + 1
355            names[name]   = group
356            names[group]  = group
357            groups[group] = name
358            data[group]   = { }
359            local csname  = "math" .. nospaces(name) .. "dictionary"
360            texintegerdef(csname,group,"immutable")
361        end
362        return group
363    end
364
365    function dictionaries.registergroupset(name,set)
366        local s = sets[name]
367        if not s then
368            local d = dictionaries.registergroup(name)
369            local n = sets.n + 1
370            local l = utilities.parsers.settings_to_array(set)
371            local g = { }
372            for i=1,#l do
373                local n = names[l[i]]
374                if n then
375                    g[#g+1] = n -- ordered
376                end
377            end
378            s = {
379                names  = l,
380                groups = g,
381                group  = d,
382            }
383            sets[name] = s
384            sets[d]    = s
385
386        end
387    end
388
389    function dictionaries.groupset(name)
390        return sets[name] or { }
391    end
392
393    function dictionaries.groupsetgroup(name)
394        local s = sets[name]
395        if s then
396            return s.group
397        else
398            return names[name] or 0
399        end
400    end
401
402    function dictionaries.registercharacter(group,index,description,class)
403        local d = names[group] -- can be number or string
404        if d then
405            data[d][index] = description or true
406            local v = variants[index]
407            if type(class) == "string" then
408                class = classes[class]
409            end
410            if not class then
411                class = true
412            end
413            if v then
414                v[d] = class
415            else
416                variants[index] = { [d] = class }
417            end
418            if not defaults[index] then
419                defaults[index] = d
420            end
421        end
422    end
423
424    implement {
425        name      = "registergroupset",
426        arguments = "2 strings",
427        actions   = dictionaries.registergroupset,
428    }
429
430    implement {
431        name      = "groupsetgroup",
432        arguments = "string",
433        actions   = { dictionaries.groupsetgroup, context },
434    }
435
436    local f_dictionary = false
437    local whatdetail   = "all"
438
439    local function trace(n,properties,group,index,font,char)
440     -- local properties, group, index, font, char = nodes.nuts.getchardict(nodes.nuts.tonut(n))
441        if whatdetail and (properties ~= 0 or group ~= 0 or index ~= 0) then
442            local char = fontchardata[font][char]
443            if char or whatdetail == "all" then
444                local unicode = char and char.unicode
445                if unicode then
446                    local groupname = groups[group]
447                    local indexname = false
448                    if groupname then
449                        indexname = data[group][index] -- dictionaries.data
450                    else
451                        groupname = "unknown"
452                    end
453                    if not indexname or indexname == true then
454                        indexname = chardata[unicode]
455                        indexname = indexname and indexname.description or "unknown"
456                    end
457                    if not f_dictionary then
458                        f_dictionary = formatters["properties [%04X:%04X:%04X] [%s] %U : %s"]
459                    end
460                    return f_dictionary(properties,group,index,groupname,unicode,indexname)
461                end
462            end
463        end
464    end
465
466    trackers.register("math.dictionaries",function(v) whatdetail = v end)
467
468    callbacks.register("get_math_dictionary",trace,"provide math dictionary details")
469
470    -- This is experimental and a prelude to the long pending "relate math rendering to
471    -- some field" wish. In TeX characters and symbols are grouped by class but that is
472    -- mostly related to spacing etc. while we actually want to group by meaning. A
473    -- reasonable but incomplete starting point is:
474    --
475    -- https://www.w3.org/TR/MathML3/appendixa.html#parsing_DefEncAtt
476    --
477    -- But it has some weird short names mixed with long ones (and a strange suddenly
478    -- uppercase: Differential-Operator) but we are not bound to that at all. We will
479    -- probably remove and add categories anyway. This openmath stuff looks a bit
480    -- abandoned but we can use it as a start and playground anyway.
481    --
482    -- The char-def.lua file will have mathgroup entries reflecting this.
483    --
484    -- This is a good one (with nice roll-overs too):
485    --
486    -- https://en.wikipedia.org/wiki/List_of_mathematical_symbols_by_subject
487
488    if environment.initex then
489
490        local registergroup = mathematics.dictionaries.registergroup
491
492        registergroup("default")
493        registergroup("binary arithmetic")
494        registergroup("binary linear algebra")
495        registergroup("binary logical")
496        registergroup("binary relation")
497        registergroup("binary set")
498        registergroup("constant arithmetic")
499        registergroup("constant set")
500        registergroup("differential")
501        registergroup("integral")
502        registergroup("interval")
503        registergroup("lambda")
504        registergroup("limit")
505        registergroup("nary arithmetic")
506        registergroup("nary constructor")
507        registergroup("nary functional")
508        registergroup("nary linear algebra")
509        registergroup("nary logical")
510        registergroup("nary minmax")
511        registergroup("nary relation")
512        registergroup("nary set list")
513        registergroup("nary set relation")
514        registergroup("nary set")
515        registergroup("nary statistics")
516        registergroup("partial") -- partial differential
517        registergroup("product")
518        registergroup("quantifier")
519        registergroup("unary arithmetic")
520        registergroup("unary elementary")
521        registergroup("unary functional")
522        registergroup("unary linear algebra")
523        registergroup("unary logical")
524        registergroup("unary set")
525        registergroup("unary vector")
526
527    end
528
529    -- \Umathdictdef\vdash 1 \mathbinarylogicaldictionary "22A2 \mathrelationcode 0 "22A2
530    --
531    -- \startluacode
532    --     mathematics.dictionaries.registercharacter("binary logical",0x22A2,"implies")
533    -- \stopluacode
534
535end
536
537do
538
539    local skip = {
540       [accent_class]       = true,
541       [topaccent_class]    = true,
542       [bottomaccent_class] = true,
543       [over_class]         = true,
544       [under_class]        = true,
545       [radical_class]      = true,
546       [root_class]         = true,
547    }
548
549    local registercharacter = mathematics.dictionaries.registercharacter
550    local groupnames        = mathematics.dictionaries.names
551
552    local setmathcharacter = function(class,family,slot,unicode,mset,dset,group)
553        if mset and class ~= ordinary_class then
554            setmathcode("global",slot,class,family,unicode)
555            mset = false
556        end
557     -- if dset and (class == open_class or class == close_class or class == middle_class or class == division_class) then
558     --     setdelcode("global",slot,family,unicode,0,0)
559     --     dset = false
560     -- end
561        if group then
562            group = groupnames[group] or 0
563            if group ~= 0 then
564                -- which one
565                registercharacter(group,unicode,nil,class)
566             -- registercharacter(group,slot,nil,class)
567            end
568        end
569        return mset, dset
570    end
571
572    local function report(class,family,unicode,name)
573        local nametype = type(name)
574        if nametype == "string" then
575            report_math("class %a, family %a, char %C, name %a",class,family,unicode,name)
576        elseif nametype == "number" then
577            report_math("class %a, family %a, char %C, number %U",class,family,unicode,name)
578        else
579            report_math("class %a, family %a, char %C",class,family,unicode)
580        end
581    end
582
583    local texmathchardef = tex.mathchardef
584
585 -- local setmathsymbol = function(name,class,family,slot,stretch,group) -- hex is nicer for tracing
586 --     if skip[class] then
587 --         return -- only in mkiv
588 --     elseif class == open_class or class == close_class or class == middle_class then
589 --         setdelcode("global",slot,family,slot,0,0) -- can go
590 --     elseif class == delimiter_class then -- open close or middle (bars)
591 --         setdelcode("global",slot,family,slot,0,0) -- can go
592 --         class = 0
593 --     else
594 --         if group then
595 --             group = groupnames[group] or 0
596 --             if group ~= 0 then
597 --                 texmathchardef(name,class,family,slot,"permanent",0x1,group,slot)
598 --                 return
599 --             end
600 --         end
601 --     end
602 --     texmathchardef(name,class,family,slot,"permanent")
603 -- end
604
605    local setmathsymbol = function(name,class,family,slot,stretch,group) -- hex is nicer for tracing
606        if skip[class] then
607            return -- only in mkiv
608     -- elseif class == open_class or class == close_class or class == middle_class then
609        else
610            if class == delimiter_class then -- open close or middle (bars)
611                class = ordinary_class
612            end
613            if group then
614                group = groupnames[group] or 0
615                if group ~= 0 then
616                    texmathchardef(name,class,family,slot,"permanent",0x1,group,slot)
617                    return
618                end
619            end
620            texmathchardef(name,class,family,slot,"permanent")
621        end
622    end
623
624    function mathematics.define()
625        if trace_defining then
626            logs.startfilelogging(report_math,"math defined from character definitions")
627        end
628        local family = 0
629        local data   = characters.data
630        --
631        local function remap(first,last)
632            for unicode=utfbyte(first),utfbyte(last) do
633                setmathcode("global",unicode,ordinary_class,family,unicode)
634            end
635        end
636        remap("0","9")
637        remap("A","Z")
638        remap("a","z")
639        --
640        setdelcode("global",0x2E,0,0,0,0) -- period is special
641        --
642        for unicode, character in sortedhash(data) do
643            local symbol = character.mathsymbol
644            local mset   = true
645            local dset   = true
646            if symbol then
647                local other = data[symbol]
648                local class = other.mathclass
649                if class then
650                    local engine = toengineclass(class)
651                    if trace_defining then
652                        report(engine,family,unicode,symbol)
653                    end
654                    mset, dset = setmathcharacter(engine,family,unicode,symbol,mset,dset,group)
655                end
656                local spec = other.mathspec
657                if spec then
658                    for i=1,#spec do
659                        local m = spec[i]
660                        local class = m.class
661                        if class then
662                            local engine = toengineclass(class)
663                            -- todo: trace
664                            mset, dset = setmathcharacter(engine,family,unicode,symbol,mset,dset,group)
665                        end
666                    end
667                end
668            end
669            local class   = character.mathclass
670            local spec    = character.mathspec
671            local name    = character.mathname
672            local stretch = character.mathstretch
673            local group   = character.mathgroup
674            if spec then
675                local done = false
676                if class then
677                    if name then
678                        report_math("fatal error, conflicting mathclass and mathspec for %C",unicode)
679                        os.exit()
680                    else
681                        class = classes[class] or ordinary_class
682                        local engine = toengineclass(class)
683                        if trace_defining then
684                            report(engine,family,unicode)
685                        end
686                        mset, dset = setmathcharacter(engine,family,unicode,unicode,mset,dset,group)
687                        done = true
688                    end
689                end
690                for i=1,#spec do
691                    local m       = spec[i]
692                    local name    = m.name
693                    local class   = m.class   or class
694                    local group   = m.group   or group
695                    local stretch = m.stretch or stretch
696                    if class then
697                        class = classes[class] or ordinary_class
698                    else
699                        class = ordinary_class
700                    end
701                    if class then
702                        local engine = toengineclass(class)
703                        if name then
704                            if trace_defining then
705                                report(engine,family,unicode,name)
706                            end
707                            setmathsymbol(name,engine,family,unicode,stretch,group)
708                        else
709                            name = (class == classes.ordinary or class == classes.digit) and character.adobename -- bad
710                            if name and trace_defining then
711                                report(engine,family,unicode,name)
712                            end
713                        end
714                        if not done then
715                            mset, dset = setmathcharacter(engine,family,unicode,m.unicode or unicode,mset,dset,group) -- see solidus
716                            done = true
717                        end
718                    end
719                end
720            else
721                if class then
722                    class = classes[class] or ordinary_class
723                else
724                    class = ordinary_class
725                end
726                if name ~= nil then
727                    local engine = toengineclass(class)
728                    if name == false then
729                        if trace_defining then
730                            report(engine,family,unicode,name)
731                        end
732                        mset, dset = setmathcharacter(engine,family,unicode,unicode,mset,dset,group)
733                    else
734                     -- if not name then
735                     --     name = character.contextname -- too dangerous, we loose textslash and a few more
736                     -- end
737                        if name then
738                            if trace_defining then
739                                report(engine,family,unicode,name)
740                            end
741                            setmathsymbol(name,engine,family,unicode,stretch,group)
742                        else
743                            if trace_defining then
744                                report(engine,family,unicode,character.adobename)
745                            end
746                        end
747                        mset, dset = setmathcharacter(engine,family,unicode,unicode,mset,dset,group)
748                    end
749                elseif class ~= ordinary_class then
750                    local engine = toengineclass(class)
751                    if trace_defining then
752                        report(engine,family,unicode,character.adobename)
753                    end
754                    mset, dset = setmathcharacter(engine,family,unicode,unicode,mset,dset,group)
755                end
756            end
757        end
758        --
759        if trace_defining then
760            logs.stopfilelogging()
761        end
762    end
763
764end
765
766-- needed for mathml analysis
767-- string with # > 1 are invalid
768-- we could cache
769
770do
771
772    local lpegmatch = lpeg.match
773    local utf8byte  = lpeg.patterns.utf8byte * lpeg.P(-1)
774
775    -- function somechar(c)
776    --     local b = lpegmatch(utf8byte,c)
777    --     return b and chardata[b]
778    -- end
779
780    local somechar = table.setmetatableindex(function(t,k)
781        if k then
782            local b = lpegmatch(utf8byte,k)
783            local v = b and chardata[b] or false
784            t[k] = v
785            return v
786        end
787    end)
788
789    local function utfmathclass(chr, default)
790        local cd = somechar[chr]
791        return cd and cd.mathclass or default or "unknown"
792    end
793
794    local function utfmathlimop(chr)
795        local cd = somechar[chr]
796        return cd and (cd.mathclass == "operator" or cd.mathclass == "integral") or false
797    end
798
799    local function utfmathaccent(chr,default,asked1,asked2)
800        local cd = somechar[chr]
801        if not cd then
802            return default or false
803        end
804        if asked1 and asked1 ~= "" then
805            local mc = cd.mathclass
806            if mc and (mc == asked1 or mc == asked2) then
807                return true
808            end
809            local ms = cd.mathspec
810            if not ms then
811                local mp = cd.mathparent
812                if mp then
813                    ms = chardata[mp].mathspec
814                end
815            end
816            if ms then
817                for i=1,#ms do
818                    local msi = ms[i]
819                    local mc = msi.class
820                    if mc and (mc == asked1 or mc == asked2) then
821                        return true
822                    end
823                end
824            end
825        else
826            local mc = cd.mathclass
827            if mc then
828                return accents[mc] or default or false
829            end
830            local ms = cd.mathspec
831            if ms then
832                for i=1,#ms do
833                    local msi = ms[i]
834                    local mc = msi.class
835                    if mc then
836                        return accents[mc] or default or false
837                    end
838                end
839            end
840        end
841        return default or false
842    end
843
844    local function utfmathstretch(chr,default) -- "h", "v", "b", ""
845        local cd = somechar[chr]
846        return cd and cd.mathstretch or default or ""
847    end
848
849    local function utfmathcommand(chr,default,asked1,asked2)
850        local cd = somechar[chr]
851        if not cd then
852            return default or ""
853        end
854        if asked1 then
855            local mn = cd.mathname
856            local mc = cd.mathclass
857            if mn and mc and (mc == asked1 or mc == asked2) then
858                return mn
859            end
860            local ms = cd.mathspec
861            if not ms then
862                local mp = cd.mathparent
863                if mp then
864                    ms = chardata[mp].mathspec
865                end
866            end
867            if ms then
868                for i=1,#ms do
869                    local msi = ms[i]
870                    local mn = msi.name
871                    if mn then
872                        local mc = msi.class
873                        if mc == asked1 or mc == asked2 then
874                            return mn
875                        end
876                    end
877                end
878            end
879        else
880            local mn = cd.mathname
881            if mn then
882                return mn
883            end
884            local ms = cd.mathspec
885            if ms then
886                for i=1,#ms do
887                    local msi = ms[i]
888                    local mn = msi.name
889                    if mn then
890                        return mn
891                    end
892                end
893            end
894        end
895        return default or ""
896    end
897
898    local function utfmathfiller(chr, default)
899        local cd = somechar[chr]
900        local cmd = cd and cd.mathfiller -- or cd.mathname
901        return cmd or default or ""
902    end
903
904    mathematics.utfmathclass   = utfmathclass
905    mathematics.utfmathstretch = utfmathstretch
906    mathematics.utfmathcommand = utfmathcommand
907    mathematics.utfmathfiller  = utfmathfiller
908    mathematics.utfmathaccent  = utfmathaccent
909
910    -- interfaced
911
912    implement {
913        name      = "utfmathclass",
914        public    = true,
915        actions   = { utfmathclass, context },
916        arguments = "argument"
917    }
918
919    implement {
920        name      = "utfmathstretch",
921        public    = true,
922        actions   = { utfmathstretch, context },
923        arguments = "argument"
924    }
925
926    implement {
927        name      = "utfmathcommand",
928        public    = true,
929        actions   = { utfmathcommand, context },
930        arguments = "argument"
931    }
932
933    implement {
934        name      = "utfmathfiller",
935        public    = true,
936        actions   = { utfmathfiller, context },
937        arguments = "argument"
938    }
939
940    implement {
941        name      = "utfmathcommandabove",
942        public    = true,
943        actions   = { utfmathcommand, context },
944        arguments = { "argument", false, "'topaccent'","'over'" }
945    }
946
947    implement {
948        name      = "utfmathcommandbelow",
949        public    = true,
950        actions   = { utfmathcommand, context },
951        arguments = { "argument", false, "'bottomaccent'","'under'" }
952    }
953
954    implement {
955        name      = "utfmathcommandfiller",
956        public    = true,
957        actions   = { utfmathfiller, context },
958        arguments = "argument"
959    }
960
961    -- todo: make this a helper:
962
963    implement {
964        name      = "doifelseutfmathabove",
965        public    = true,
966        actions   = { utfmathaccent, ctx_doifelsesomething },
967        arguments = { "argument", false, "'topaccent'", "'over'" }
968    }
969
970    implement {
971        name      = "doifelseutfmathbelow",
972        public    = true,
973        actions   = { utfmathaccent, ctx_doifelsesomething },
974        arguments = { "argument", false, "'bottomaccent'", "'under'" }
975    }
976
977    implement {
978        name      = "doifelseutfmathaccent",
979        public    = true,
980        actions   = { utfmathaccent, ctx_doifelsesomething },
981        arguments = "argument",
982    }
983
984    implement {
985        name      = "doifelseutfmathfiller",
986        public    = true,
987        actions   = { utfmathfiller, ctx_doifelsesomething },
988        arguments = "argument",
989    }
990
991    implement {
992        name      = "doifelseutfmathlimop",
993        public    = true,
994        actions   = { utfmathlimop, ctx_doifelsesomething },
995        arguments = "argument"
996    }
997
998end
999
1000-- helpers
1001--
1002-- 1: step 1
1003-- 2: step 2
1004-- 3: htdp * 1.33^n
1005-- 4: size * 1.33^n
1006-- 5: use lfg
1007
1008function mathematics.big(tfmdata,unicode,n,method)
1009    local t = tfmdata.characters
1010    local c = t[unicode]
1011    if c and n > 0 then
1012        if method == 1 or method == 2 or method == 5 then
1013            if method == 5 then
1014                local b = tfmdata.bigslots
1015                if b then
1016                    n = (n > #b and b[#b]) or b[n] or n
1017                end
1018            elseif method == 2 then -- large steps
1019                n = n * 2
1020            end
1021            local next = c.next
1022            while next do
1023                if n <= 1 then
1024                    return next
1025                else
1026                    n = n - 1
1027                    local tn = t[next].next
1028                    if tn then
1029                        next = tn
1030                    else
1031                        return next
1032                    end
1033                end
1034            end
1035        elseif method >= 3 then
1036            local size = 1.33^n
1037            if method == 4 then
1038                size = tfmdata.parameters.size * size
1039            else -- if method == 3 then
1040                size = (c.height + c.depth) * size
1041            end
1042            local next = c.next
1043            while next do
1044                local cn = t[next]
1045                if (cn.height + cn.depth) >= size then
1046                    return next
1047                else
1048                    local tn = cn.next
1049                    if tn then
1050                        next = tn
1051                    else
1052                        return next
1053                    end
1054                end
1055            end
1056        end
1057    end
1058    return unicode
1059end
1060
1061do -- experimental
1062
1063    local categories       = { }
1064    mathematics.categories = categories
1065
1066    local a_mathcategory   = attributes.private("mathcategory")
1067
1068    local functions        = storage.allocate()
1069    categories.functions   = functions
1070    local noffunctions     = 1000 -- offset
1071
1072    implement {
1073        name      = "tagmfunctiontxt",
1074        arguments = { "string", "conditional" },
1075        actions   = function(tag,apply)
1076            local delta = apply and 1000 or 0
1077            texsetattribute(a_mathcategory,1000 + delta)
1078        end
1079    }
1080
1081    implement {
1082        name      = "tagmfunctionlab",
1083        arguments = { "string", "conditional" },
1084        actions   = function(tag,apply)
1085            local delta = apply and 1000 or 0
1086            local n = functions[tag]
1087            if not n then
1088                noffunctions = noffunctions + 1
1089                functions[noffunctions] = tag
1090                functions[tag] = noffunctions
1091                texsetattribute(a_mathcategory,noffunctions + delta)
1092            else
1093                texsetattribute(a_mathcategory,n + delta)
1094            end
1095        end
1096    }
1097
1098end
1099
1100do
1101
1102    local list
1103
1104    function mathematics.resetattributes()
1105        if not list then
1106            list = { }
1107            for k, v in next, attributes.numbers do
1108                if find(k,"^math") then
1109                    list[#list+1] = v
1110                end
1111            end
1112        end
1113        for i=1,#list do
1114            texsetattribute(list[i],unsetvalue)
1115        end
1116    end
1117
1118end
1119
1120implement {
1121    name      = "resetmathattributes",
1122    public    = true,
1123    protected = true,
1124    actions   = mathematics.resetattributes
1125}
1126
1127-- weird to do this here but it's a side affect of math anyway
1128
1129implement {
1130    name     = "enableasciimode",
1131    onlyonce = true,
1132    actions  = resolvers.macros.enablecomment,
1133}
1134
1135implement {
1136    name      = "nofmathvariants",
1137    public    = true,
1138    usage     = "value",
1139    arguments = "integer",
1140    actions   = function(n)
1141        local char = fontchardata[getfontoffamily(0)]
1142        local data = char[n]
1143        local size = 1
1144        while data do
1145            local next = data.next
1146            if next then
1147                size = size + 1
1148                data = char[next]
1149            else
1150                break
1151            end
1152        end
1153        return integer_value, size
1154    end,
1155}
1156
1157implement {
1158    name      = "getmathvariant",
1159    public    = true,
1160    usage     = "value",
1161    arguments = "2 integers",
1162    actions   = function(m,n)
1163        local char = fontchardata[getfontoffamily(0)]
1164        local data = char[n]
1165        local slot = n
1166        if data then
1167            while data and m > 1 do
1168                local next = data.next
1169                if next then
1170                    m = m - 1
1171                    data = char[next]
1172                    slot = next
1173                else
1174                    break
1175                end
1176            end
1177        end
1178        return integer_value, slot
1179    end,
1180}
1181
1182implement {
1183    name      = "getmathcharone",
1184    public    = true,
1185    usage     = "value",
1186    arguments = "integer",
1187    actions   = function(n)
1188        local char = fontchardata[getfontoffamily(0)]
1189        local data = char[n]
1190        return integer_value, data and data.smaller or n
1191    end,
1192}
1193
1194implement {
1195    name      = "getmathchartwo",
1196    public    = true,
1197    usage     = "value",
1198    arguments = "integer",
1199    actions   = function(n)
1200        local char = fontchardata[getfontoffamily(0)]
1201        local data = char[n]
1202        local slot = data and data.smaller
1203        if slot then
1204            data = char[slot]
1205            n = slot
1206        end
1207        return integer_value, data and data.smaller or n
1208    end,
1209}
1210