math-ini.lua /size: 24 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-- if needed we can use the info here to set up xetex definition files
10-- the "8000 hackery influences direct characters (utf) as indirect \char's
11--
12-- isn't characters.data loaded already ... shortcut it here
13--
14-- replace code 7 by 0 as we don't use it anyway (chars with code 7 will adapt to
15-- to the fam when set ... we use other means .. ok, we could use it for spacing but
16-- then we also have to set the other characters (only a subset done now)
17
18local next, type = next, type
19local formatters, find = string.formatters, string.find
20local utfchar, utfbyte, utflength = utf.char, utf.byte, utf.length
21----- floor = math.floor
22local sortedhash = table.sortedhash
23local toboolean = toboolean
24
25local context               = context
26local commands              = commands
27local implement             = interfaces.implement
28
29local ctx_sprint            = context.sprint
30local ctx_doifelsesomething = commands.doifelsesomething
31
32local trace_defining        = false  trackers.register("math.defining", function(v) trace_defining = v end)
33
34-- trace_defining = true
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
51
52local families = allocate {
53    mr = 0,
54    mb = 1,
55}
56
57--- to be checked  .. afew defaults in char-def that should be alpha
58
59local classes = allocate {
60    ord          =  0, -- mathordcomm     mathord
61    op           =  1, -- mathopcomm      mathop
62    bin          =  2, -- mathbincomm     mathbin
63    rel          =  3, -- mathrelcomm     mathrel
64    open         =  4, -- mathopencomm    mathopen
65    middle       =  4,
66    close        =  5, -- mathclosecomm   mathclose
67    punct        =  6, -- mathpunctcomm   mathpunct
68    alpha        =  7, -- mathalphacomm   firstofoneargument
69    accent       =  8, -- class 0
70    radical      =  9,
71    xaccent      = 10, -- class 3
72    topaccent    = 11, -- class 0
73    botaccent    = 12, -- class 0
74    under        = 13,
75    over         = 14,
76    delimiter    = 15,
77    division     = 15,
78    inner        =  0, -- mathinnercomm   mathinner
79    choice       =  0, -- mathchoicecomm  @@mathchoicecomm
80    prime        =  0,
81    differential =  0,
82    exponential  =  0,
83    limop        =  1, -- mathlimopcomm   @@mathlimopcomm
84    nolop        =  1, -- mathnolopcomm   @@mathnolopcomm
85    integral     =  1, -- new in lmtx but here still an operator
86    --
87    ordinary     =  0, -- ord
88    operator     =  1, -- op
89    alphabetic   =  7, -- alpha
90    punctuation  =  6, -- punct
91    opening      =  4, -- open
92    closing      =  5, -- close
93    binary       =  2, -- bin
94    relation     =  3, -- rel
95    diacritic    =  8, -- accent
96    large        =  1, -- op
97    variable     =  7, -- alphabetic
98    number       =  7, -- alphabetic
99    root         = 16, -- a private one
100}
101
102local engineclasses = table.setmetatableindex(function(t,k)
103    if k then
104        local c = tonumber(k) or classes[k] or 0
105        local v = c < 8 and c or 0
106        t[k] = v
107        return v
108    else
109        return 0
110    end
111end)
112
113local ordinary_class  = classes.ordinary
114local open_class      = classes.open
115local middle_class    = classes.middle
116local close_class     = classes.close
117local accent_class    = classes.accent
118local radical_class   = classes.radical
119local topaccent_class = classes.topaccent
120local botaccent_class = classes.botaccent
121local under_class     = classes.under
122local over_class      = classes.over
123local delimiter_class = classes.delimiter
124local division_class  = classes.division
125local root_class      = classes.root
126
127local accents = allocate {
128    accent    = true, -- some can be both
129    topaccent = true,  [topaccent_class] = true,
130    botaccent = true,  [botaccent_class] = true,
131    under     = true,  [under_class]     = true,
132    over      = true,  [over_class]      = true,
133    unknown   = false,
134}
135
136-- engine subtypes get from elsewhere
137
138local codes = allocate {
139    ordinary       = 0,  [ 0] = "ordinary",
140    largeoperator  = 1,  [ 1] = "largeoperator",
141    binaryoperator = 2,  [ 2] = "binaryoperator",
142    relation       = 3,  [ 3] = "relation",
143    openingsymbol  = 4,  [ 4] = "openingsymbol",
144    closingsymbol  = 5,  [ 5] = "closingsymbol",
145    punctuation    = 6,  [ 6] = "punctuation",
146 -- inner          = 7,  [ 7] = "inner",
147 -- undersymbol    = 8,  [ 8] = "undersymbol",
148 -- oversymbol     = 9,  [ 9] = "oversymbol",
149 -- fractionsymbol = 10, [10] = "fractionsymbol",
150 -- radicalsymbol  = 11, [11] = "radicalsymbol",
151    middlesymbol   = 12, [12] = "middlesymbol",
152}
153
154local extensibles = allocate {
155           unknown    = 0,
156    l = 1, left       = 1,
157    r = 2, right      = 2,
158    h = 3, horizontal = 3,-- lr or rl
159    u = 5, up         = 4,
160    d = 5, down       = 5,
161    v = 6, vertical   = 6,-- ud or du
162    m = 7, mixed      = 7,
163}
164
165table.setmetatableindex(extensibles,function(t,k) t[k] = 0 return 0 end)
166
167local virtualized = allocate {
168}
169
170function mathematics.virtualize(unicode,virtual)
171
172    local function virtualize(k,v)
173        local c = virtualized[k]
174        if c == v then
175            report_math("character %C is already virtualized to %C",k,v)
176        elseif c then
177            report_math("character %C is already virtualized to %C, ignoring mapping to %C",k,c,v)
178        else
179            virtualized[k] = v
180        end
181    end
182
183    if type(unicode) == "table" then
184        for k, v in next, unicode do
185            virtualize(k,v)
186        end
187    elseif type(unicode) == "number" and type(virtual) == "number" then
188        virtualize(unicode,virtual)
189 -- else
190        -- error
191    end
192end
193
194mathematics.extensibles = extensibles
195mathematics.classes     = classes
196mathematics.codes       = codes
197-----------.accents     = codes
198mathematics.families    = families
199mathematics.virtualized = virtualized
200
201local escapes = characters.filters.utf.private.escapes
202
203do
204
205    local setmathcharacter = function(class,family,slot,unicode,mset,dset)
206        if mset and codes[class] then -- regular codes < 7
207            setmathcode("global",slot,class,family,unicode)
208            mset = false
209        end
210        if dset and (class == open_class or class == close_class or class == middle_class or class == division_class) then
211            setdelcode("global",slot,family,unicode,0,0)
212            dset = false
213        end
214        return mset, dset
215    end
216
217    local function report(class,engine,family,unicode,name)
218        local nametype = type(name)
219        if nametype == "string" then
220            report_math("class %a, engine %a, family %a, char %C, name %a",class,engine,family,unicode,name)
221        elseif nametype == "number" then
222            report_math("class %a, engine %a, family %a, char %C, number %U",class,engine,family,unicode,name)
223        else
224            report_math("class %a, engine %a, family %a, char %C",class,engine,family,unicode)
225        end
226    end
227
228    local f_accent    = formatters[ [[\defUmathtopaccent \%s{%X}{%X}{%X}]] ]
229    local f_topaccent = formatters[ [[\defUmathtopaccent \%s{%X}{%X}{%X}]] ]
230    local f_botaccent = formatters[ [[\defUmathbotaccent \%s{%X}{%X}{%X}]] ]
231    local f_over      = formatters[ [[\defUdelimiterover \%s{%X}{%X}{%X}]] ]
232    local f_under     = formatters[ [[\defUdelimiterunder\%s{%X}{%X}{%X}]] ]
233    local f_fence     = formatters[ [[\defUdelimiter     \%s{%X}{%X}{%X}]] ]
234    local f_delimiter = formatters[ [[\defUdelimiter     \%s{%X}{%X}{%X}]] ]
235    local f_radical   = formatters[ [[\defUradical       \%s{%X}{%X}]]     ]
236    local f_root      = formatters[ [[\defUroot          \%s{%X}{%X}]]     ]
237    local f_char      = formatters[ [[\defUmathchar      \%s{%X}{%X}{%X}]] ]
238
239    local texmathchardef = tex.mathchardef
240
241    local setmathsymbol = function(name,class,engine,family,slot) -- hex is nicer for tracing
242        if class == accent_class then
243            ctx_sprint(f_topaccent(name,0,family,slot))
244        elseif class == topaccent_class then
245            ctx_sprint(f_topaccent(name,0,family,slot))
246        elseif class == botaccent_class then
247            ctx_sprint(f_botaccent(name,0,family,slot))
248        elseif class == over_class then
249            ctx_sprint(f_over(name,0,family,slot))
250        elseif class == under_class then
251            ctx_sprint(f_under(name,0,family,slot))
252        elseif class == open_class or class == close_class or class == middle_class then
253            setdelcode("global",slot,{family,slot,0,0})
254            ctx_sprint(f_fence(name,engine,family,slot))
255        elseif class == delimiter_class then
256            setdelcode("global",slot,{family,slot,0,0})
257            ctx_sprint(f_delimiter(name,0,family,slot))
258        elseif class == radical_class then
259            ctx_sprint(f_radical(name,family,slot))
260        elseif class == root_class then
261            ctx_sprint(f_root(name,family,slot))
262        elseif texmathchardef then
263            texmathchardef(name,engine,family,slot,"permanent")
264        else
265            -- beware, open/close and other specials should not end up here
266            ctx_sprint(f_char(name,engine,family,slot))
267        end
268    end
269
270    function mathematics.define(family)
271        family = family or 0
272        family = families[family] or family
273        local data = characters.data
274        --
275        local function remap(first,last)
276            for unicode=utfbyte(first),utfbyte(last) do
277                setmathcode("global",unicode,ordinary_class,family,unicode)
278            end
279        end
280        remap("0","9")
281        remap("A","Z")
282        remap("a","z")
283        --
284        for unicode, character in sortedhash(data) do
285            local symbol = character.mathsymbol
286            local mset   = true
287            local dset   = true
288            if symbol then
289                local other = data[symbol]
290                local class = other.mathclass
291                if class then
292                    local engine = engineclasses[class]
293                    if trace_defining then
294                        report(class,engine,family,unicode,symbol)
295                    end
296                    mset, dset = setmathcharacter(engine,family,unicode,symbol,mset,dset)
297                end
298                local spec = other.mathspec
299                if spec then
300                    for i=1,#spec do
301                        local m = spec[i]
302                        local class = m.class
303                        if class then
304                            local engine = engineclasses[class]
305                            -- todo: trace
306                            mset, dset = setmathcharacter(engine,family,unicode,symbol,mset,dset)
307                        end
308                    end
309                end
310            end
311            local class = character.mathclass
312            local spec  = character.mathspec
313            local name  = character.mathname
314            if spec then
315                local done = false
316                if class then
317                    if name then
318                        report_math("fatal error, conflicting mathclass and mathspec for %C",unicode)
319                        os.exit()
320                    else
321                        class = classes[class] or ordinary_class
322                        local engine = engineclasses[class]
323                        if trace_defining then
324                            report(class,engine,family,unicode)
325                        end
326                        mset, dset = setmathcharacter(engine,family,unicode,unicode,mset,dset)
327                        done = true
328                    end
329                end
330                for i=1,#spec do
331                    local m     = spec[i]
332                    local name  = m.name
333                    local class = m.class or class
334                    if class then
335                        class = classes[class] or ordinary_class
336                    else
337                        class = ordinary_class
338                    end
339                    if class then
340                        local engine = engineclasses[class]
341                        if name then
342                            if trace_defining then
343                                report(class,engine,family,unicode,name)
344                            end
345                            setmathsymbol(name,class,engine,family,unicode)
346                        else
347                            name = (class == classes.ordinary or class == classes.digit) and character.adobename -- bad
348                            if name and trace_defining then
349                                report(class,engine,family,unicode,name)
350                            end
351                        end
352                        if not done then
353                            mset, dset = setmathcharacter(engine,family,unicode,m.unicode or unicode,mset,dset) -- see solidus
354                            done = true
355                        end
356                    end
357                end
358            else
359                if class then
360                    class = classes[class] or ordinary_class
361                else
362                    class = ordinary_class
363                end
364                if name ~= nil then
365                    local engine = engineclasses[class]
366                    if name == false then
367                        if trace_defining then
368                            report(class,engine,family,unicode,name)
369                        end
370                        mset, dset = setmathcharacter(engine,family,unicode,unicode,mset,dset)
371                    else
372                     -- if not name then
373                     --     name = character.contextname -- too dangerous, we loose textslash and a few more
374                     -- end
375                        if name then
376                            if trace_defining then
377                                report(class,engine,family,unicode,name)
378                            end
379                            setmathsymbol(name,class,engine,family,unicode)
380                        else
381                            if trace_defining then
382                                report(class,engine,family,unicode,character.adobename)
383                            end
384                        end
385                        mset, dset = setmathcharacter(engine,family,unicode,unicode,mset,dset)
386                    end
387                elseif class ~= ordinary_class then
388                    local engine = engineclasses[class]
389                    if trace_defining then
390                        report(class,engine,family,unicode,character.adobename)
391                    end
392                    mset, dset = setmathcharacter(engine,family,unicode,unicode,mset,dset)
393                end
394            end
395        end
396        if trace_defining then
397            logs.stopfilelogging()
398        end
399    end
400
401end
402
403-- needed for mathml analysis
404-- string with # > 1 are invalid
405-- we could cache
406
407local lpegmatch = lpeg.match
408
409local utf8byte  = lpeg.patterns.utf8byte * lpeg.P(-1)
410
411-- function somechar(c)
412--     local b = lpegmatch(utf8byte,c)
413--     return b and chardata[b]
414-- end
415
416
417local somechar = { }
418
419table.setmetatableindex(somechar,function(t,k)
420    if k then
421        local b = lpegmatch(utf8byte,k)
422        local v = b and chardata[b] or false
423        t[k] = v
424        return v
425    end
426end)
427
428local function utfmathclass(chr, default)
429    local cd = somechar[chr]
430    return cd and cd.mathclass or default or "unknown"
431end
432
433local function utfmathlimop(chr)
434    local cd = somechar[chr]
435    return cd and (cd.mathclass == "operator" or cd.mathclass == "integral") or false
436end
437
438local function utfmathaccent(chr,default,asked1,asked2)
439    local cd = somechar[chr]
440    if not cd then
441        return default or false
442    end
443    if asked1 and asked1 ~= "" then
444        local mc = cd.mathclass
445        if mc and (mc == asked1 or mc == asked2) then
446            return true
447        end
448        local ms = cd.mathspec
449        if not ms then
450            local mp = cd.mathparent
451            if mp then
452                ms = chardata[mp].mathspec
453            end
454        end
455        if ms then
456            for i=1,#ms do
457                local msi = ms[i]
458                local mc = msi.class
459                if mc and (mc == asked1 or mc == asked2) then
460                    return true
461                end
462            end
463        end
464    else
465        local mc = cd.mathclass
466        if mc then
467            return accents[mc] or default or false
468        end
469        local ms = cd.mathspec
470        if ms then
471            for i=1,#ms do
472                local msi = ms[i]
473                local mc = msi.class
474                if mc then
475                    return accents[mc] or default or false
476                end
477            end
478        end
479    end
480    return default or false
481end
482
483local function utfmathstretch(chr,default) -- "h", "v", "b", ""
484    local cd = somechar[chr]
485    return cd and cd.mathstretch or default or ""
486end
487
488local function utfmathcommand(chr,default,asked1,asked2)
489    local cd = somechar[chr]
490    if not cd then
491        return default or ""
492    end
493    if asked1 then
494        local mn = cd.mathname
495        local mc = cd.mathclass
496        if mn and mc and (mc == asked1 or mc == asked2) then
497            return mn
498        end
499        local ms = cd.mathspec
500        if not ms then
501            local mp = cd.mathparent
502            if mp then
503                ms = chardata[mp].mathspec
504            end
505        end
506        if ms then
507            for i=1,#ms do
508                local msi = ms[i]
509                local mn = msi.name
510                if mn then
511                    local mc = msi.class
512                    if mc == asked1 or mc == asked2 then
513                        return mn
514                    end
515                end
516            end
517        end
518    else
519        local mn = cd.mathname
520        if mn then
521            return mn
522        end
523        local ms = cd.mathspec
524        if ms then
525            for i=1,#ms do
526                local msi = ms[i]
527                local mn = msi.name
528                if mn then
529                    return mn
530                end
531            end
532        end
533    end
534    return default or ""
535end
536
537local function utfmathfiller(chr, default)
538    local cd = somechar[chr]
539    local cmd = cd and cd.mathfiller -- or cd.mathname
540    return cmd or default or ""
541end
542
543mathematics.utfmathclass   = utfmathclass
544mathematics.utfmathstretch = utfmathstretch
545mathematics.utfmathcommand = utfmathcommand
546mathematics.utfmathfiller  = utfmathfiller
547mathematics.utfmathaccent  = utfmathaccent
548
549-- interfaced
550
551implement {
552    name      = "utfmathclass",
553    actions   = { utfmathclass, context },
554    arguments = "string"
555}
556
557implement {
558    name      = "utfmathstretch",
559    actions   = { utfmathstretch, context },
560    arguments = "string"
561}
562
563implement {
564    name      = "utfmathcommand",
565    actions   = { utfmathcommand, context },
566    arguments = "string"
567}
568
569implement {
570    name      = "utfmathfiller",
571    actions   = { utfmathfiller, context },
572    arguments = "string"
573}
574
575implement {
576    name      = "utfmathcommandabove",
577    actions   = { utfmathcommand, context },
578    arguments = { "string", false, "'topaccent'","'over'" }
579}
580
581implement {
582    name      = "utfmathcommandbelow",
583    actions   = { utfmathcommand, context },
584    arguments = { "string", false, "'botaccent'","'under'" }
585}
586
587implement {
588    name      = "utfmathcommandfiller",
589    actions   = { utfmathfiller, context },
590    arguments = "string"
591}
592
593-- todo: make this a helper:
594
595implement {
596    name      = "doifelseutfmathabove",
597    actions   = { utfmathaccent, ctx_doifelsesomething },
598    arguments = { "string", false, "'topaccent'", "'over'" }
599}
600
601implement {
602    name      = "doifelseutfmathbelow",
603    actions   = { utfmathaccent, ctx_doifelsesomething },
604    arguments = { "string", false, "'botaccent'", "'under'" }
605}
606
607implement {
608    name      = "doifelseutfmathaccent",
609    actions   = { utfmathaccent, ctx_doifelsesomething },
610    arguments = "string",
611}
612
613implement {
614    name      = "doifelseutfmathfiller",
615    actions   = { utfmathfiller, ctx_doifelsesomething },
616    arguments = "string",
617}
618
619implement {
620    name      = "doifelseutfmathlimop",
621    actions   = { utfmathlimop, ctx_doifelsesomething },
622    arguments = "string",
623}
624
625-- helpers
626--
627-- 1: step 1
628-- 2: step 2
629-- 3: htdp * 1.33^n
630-- 4: size * 1.33^n
631
632function mathematics.big(tfmdata,unicode,n,method)
633    local t = tfmdata.characters
634    local c = t[unicode]
635    if c and n > 0 then
636        local vv = c.vert_variants or c.next and t[c.next].vert_variants
637        if vv then
638            local vvn = vv[n]
639            return vvn and vvn.glyph or vv[#vv].glyph or unicode
640        elseif method == 1 or method == 2 then
641            if method == 2 then -- large steps
642                n = n * 2
643            end
644            local next = c.next
645            while next do
646                if n <= 1 then
647                    return next
648                else
649                    n = n - 1
650                    local tn = t[next].next
651                    if tn then
652                        next = tn
653                    else
654                        return next
655                    end
656                end
657            end
658        elseif method >= 3 then
659            local size = 1.33^n
660            if method == 4 then
661                size = tfmdata.parameters.size * size
662            else -- if method == 3 then
663                size = (c.height + c.depth) * size
664            end
665            local next = c.next
666            while next do
667                local cn = t[next]
668                if (cn.height + cn.depth) >= size then
669                    return next
670                else
671                    local tn = cn.next
672                    if tn then
673                        next = tn
674                    else
675                        return next
676                    end
677                end
678            end
679        end
680    end
681    return unicode
682end
683
684-- experimental
685
686-- local categories = { } -- indexed + hashed
687--
688-- local a_mathcategory = attributes.private("mathcategory")
689--
690-- local function registercategory(category,tag,data) -- always same data for tag
691--     local c = categories[category]
692--     if not c then
693--         c = { }
694--         categories[category] = c
695--     end
696--     local n = c[tag]
697--     if not n then
698--         n = #c + 1
699--         c[n] = data
700--         n = n * 1000 + category
701--         c[tag] = n
702--     end
703--     return n
704-- end
705--
706-- function mathematics.getcategory(n)
707--     local category = n % 1000
708--     return category, categories[category][floor(n/1000)]
709-- end
710--
711-- mathematics.registercategory = registercategory
712--
713-- function commands.taggedmathfunction(tag,label)
714--     if label then
715--         texsetattribute(a_mathcategory,registercategory(1,tag,tag))
716--         context.mathlabeltext(tag)
717--     else
718--         texsetattribute(a_mathcategory,1)
719--         context(tag)
720--     end
721-- end
722
723local categories       = { }
724mathematics.categories = categories
725
726local a_mathcategory = attributes.private("mathcategory")
727
728local functions    = storage.allocate()
729local noffunctions = 1000 -- offset
730
731categories.functions = functions
732
733implement {
734    name      = "tagmfunctiontxt",
735    arguments = { "string", "conditional" },
736    actions   = function(tag,apply)
737        local delta = apply and 1000 or 0
738        texsetattribute(a_mathcategory,1000 + delta)
739    end
740}
741
742implement {
743    name      = "tagmfunctionlab",
744    arguments = { "string", "conditional" },
745    actions   = function(tag,apply)
746        local delta = apply and 1000 or 0
747        local n = functions[tag]
748        if not n then
749            noffunctions = noffunctions + 1
750            functions[noffunctions] = tag
751            functions[tag] = noffunctions
752            texsetattribute(a_mathcategory,noffunctions + delta)
753        else
754            texsetattribute(a_mathcategory,n + delta)
755        end
756    end
757}
758
759--
760
761local list
762
763function mathematics.resetattributes()
764    if not list then
765        list = { }
766        for k, v in next, attributes.numbers do
767            if find(k,"^math") then
768                list[#list+1] = v
769            end
770        end
771    end
772    for i=1,#list do
773        texsetattribute(list[i],unsetvalue)
774    end
775end
776
777implement {
778    name    = "resetmathattributes",
779    actions = mathematics.resetattributes
780}
781
782-- weird to do this here but it's a side affect of math anyway
783
784interfaces.implement {
785    name     = "enableasciimode",
786    onlyonce = true,
787    actions  = resolvers.macros.enablecomment,
788}
789