if not modules then modules = { } end modules ['math-ini'] = { version = 1.001, comment = "companion to math-ini.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } -- if needed we can use the info here to set up xetex definition files -- the "8000 hackery influences direct characters (utf) as indirect \char's -- -- isn't characters.data loaded already ... shortcut it here -- -- replace code 7 by 0 as we don't use it anyway (chars with code 7 will adapt to -- to the fam when set ... we use other means .. ok, we could use it for spacing but -- then we also have to set the other characters (only a subset done now) local next, type = next, type local formatters, find, nospaces = string.formatters, string.find, string.nospaces local utfchar, utfbyte, utflength = utf.char, utf.byte, utf.length ----- floor = math.floor local sortedhash = table.sortedhash local toboolean = toboolean local context = context local commands = commands local implement = interfaces.implement local ctx_sprint = context.sprint local ctx_doifelsesomething = commands.doifelsesomething local trace_defining = false trackers.register("math.defining", function(v) trace_defining = v end) trace_defining = true local report_math = logs.reporter("mathematics","initializing") mathematics = mathematics or { } local mathematics = mathematics mathematics.extrabase = fonts.privateoffsets.mathextrabase -- here we push some virtuals mathematics.privatebase = fonts.privateoffsets.mathbase -- here we push the ex local unsetvalue = attributes.unsetvalue local allocate = utilities.storage.allocate local chardata = characters.data local texsetattribute = tex.setattribute local setmathcode = tex.setmathcode local setdelcode = tex.setdelcode local texintegerdef = tex.integerdef -- This is relatively new and experimental: do local dictionaries = { } mathematics.dictionaries = dictionaries local names = dictionaries.names or utilities.storage.allocate() local groups = dictionaries.groups or utilities.storage.allocate() local data = dictionaries.data or utilities.storage.allocate() if storage then storage.register("mathematics/dictionaries/names", names, "characters.dictionaries.names") storage.register("mathematics/dictionaries/groups", groups, "characters.dictionaries.groups") storage.register("mathematics/dictionaries/data", data, "characters.dictionaries.data") end function dictionaries.registergroup(name) local group = rawget(names,name) if not group then group = #groups + 1 names[name] = group names[group] = group groups[group] = name data[group] = { } local csname = "math" .. nospaces(name) .. "dictionary" texintegerdef(csname,group,"immutable") end return group end function dictionaries.registercharacter(group,index,description) local d = names[group] -- can be number or string if d then data[d][index] = description end end local fontchardata = fonts.hashes.characters local f_dictionary = false local register = callback.register local whatdetail = "all" local function trace(n,properties,group,index,font,char) -- local properties, group, index, font, char = nodes.nuts.getchardict(nodes.nuts.tonut(n)) local char = fontchardata[font][char] if char or whatdetail == "all" then local unicode = char and char.unicode if unicode then local groupname = groups[group] local indexname = false if groupname then indexname = data[group][index] -- dictionaries.data else groupname = "unknown" end if not indexname then indexname = chardata[unicode] indexname = indexname and indexname.description or "unknown" end if not f_dictionary then f_dictionary = formatters["properties [%04X:%04X:%04X] [%s] %U : %s"] end return f_dictionary(properties,group,index,groupname,unicode,indexname) end end end trackers.register("math.dictionaries",function(v) whatdetail = v if whatdetail then register("get_math_dictionary",trace) else register("get_math_dictionary") end end) register("get_math_dictionary",trace) -- This is experimental and a prelude to the long pending "relate math rendering to -- some field" wish. In TeX characters and symbols are grouped by class but that is -- mostly related to spacing etc. while we actually want to group by meaning. A -- reasonable but incomplete starting point is: -- -- https://www.w3.org/TR/MathML3/appendixa.html#parsing_DefEncAtt -- -- But it has some weird short names mixed with long ones (and a strange suddenly -- uppercase: Differential-Operator) but we are not bound to that at all. We will -- probably remove and add categories anyway. This openmath stuff looks a bit -- abandoned but we can use it as a start and playground anyway. -- -- The char-def.lua file will have mathgroup entries reflecting this. local registergroup = mathematics.dictionaries.registergroup registergroup("default") registergroup("binary arithmic") registergroup("binary linear algebra") registergroup("binary logical") registergroup("binary relation") registergroup("binary set") registergroup("constant arithmic") registergroup("constant set") registergroup("differential") registergroup("integral") registergroup("interval") registergroup("lambda") registergroup("limit") registergroup("nary arithmic") registergroup("nary constructor") registergroup("nary functional") registergroup("nary linear algebra") registergroup("nary logical") registergroup("nary minmax") registergroup("nary relation") registergroup("nary set list") registergroup("nary set relation") registergroup("nary set") registergroup("nary statistics") registergroup("partial") -- partial differential registergroup("product") registergroup("quantifier") registergroup("unary arithmic") registergroup("unary elementary") registergroup("unary functional") registergroup("unary linear algebra") registergroup("unary logical") registergroup("unary set") registergroup("unary vector") -- \Umathdictdef\vdash 1 \mathbinarylogicaldictionary "22A2 \mathrelationcode 0 "22A2 -- -- \startluacode -- mathematics.dictionaries.registercharacter("binary logical",0x22A2,"implies") -- \stopluacode end -- These are different from mkiv with luatex. local classes = allocate { unset = 64 } -- or -1 local classnames = allocate { } local maxengineclass = 63 local lastengineclass = 0 local lastprivateclass = maxengineclass for k, v in next, nodes.noadcodes do if type(k) == "string"then classes[k] = v local n = classnames[v] if not n or #k < #n then classnames[v] = k end elseif k > lastengineclass then lastengineclass = k end end local ordinary_class = classes.ordinary local operator_class = classes.operator local binary_class = classes.binary local relation_class = classes.relation local open_class = classes.open local close_class = classes.close local punctuation_class = classes.punctuation local middle_class = classes.middle local accent_class = classes.accent local radical_class = classes.radical local fraction_class = classes.fraction local under_class = classes.under local over_class = classes.over local fenced_class = classes.fenced local ghost_class = classes.ghost classes.ord = ordinary_class classes.op = operator_class classes.bin = binary_class classes.rel = relation_class classes.opening = open_class -- will go classes.closing = close_class -- will go classes.punct = punctuation_class classes.frac = fraction_class classes.rad = radical_class classes.fen = fenced_class classes.gst = ghost_class classes.limop = operator_class classes.limoperator = operator_class classes.nolop = operator_class classes.nolimoperator = operator_class classes.large = operator_class classes.largeoperator = operator_class -- special in the engine : variable active inner vcenter local function registerengineclass(name,short) local class = classes[name] if not class then if lastengineclass < maxengineclass then lastengineclass = lastengineclass + 1 class = lastengineclass classnames[class] = short or name else class = ordinary_class end classes[name] = class end return class end registerengineclass("explicit", "xpl") registerengineclass("imaginary", "img") registerengineclass("differential", "dif") registerengineclass("exponential", "exp") registerengineclass("ellipsis", "ell") registerengineclass("function", "fnc") registerengineclass("digit", "dig") local division_class = registerengineclass("division", "div") registerengineclass("factorial", "fac") registerengineclass("wrapped", "wra") registerengineclass("construct", "con") registerengineclass("dimension", "dim") registerengineclass("unary", "una") registerengineclass("textpunctuation", "tpu") registerengineclass("unspaced") registerengineclass("experimental") registerengineclass("fake") local specialclasses = tex.specialmathclasscodes classes["all"] = specialclasses["all"] classnames[specialclasses["all"] ] = "all" classes["begin"] = specialclasses["begin"] classnames[specialclasses["begin"]] = "begin" classes["end"] = specialclasses["end"] classnames[specialclasses["end"] ] = "end" callback.register("get_noad_class", function(n) return classnames[n] end) local function registerprivateclass(name) local class = classes[name] if not class then lastprivateclass = lastprivateclass + 1 class = lastprivateclass classes[name] = class -- also setup end return class end local function toengineclass(class) if type(class) == "string" then return classes[class] or ordinary_class elseif class > lastengineclass then return ordinary_class else return class end end implement { name = "registerengineclass", public = true, protected = true, arguments = { "optional", "optional" }, actions = registerengineclass, } local topaccent_class = registerprivateclass("topaccent") local botaccent_class = registerprivateclass("botaccent") local delimiter_class = registerprivateclass("delimiter") local root_class = registerprivateclass("root") local prime_class = registerprivateclass("prime") local accents = allocate { accent = true, -- some can be both topaccent = true, [topaccent_class] = true, botaccent = true, [botaccent_class] = true, under = true, [under_class] = true, over = true, [over_class] = true, unknown = false, } local integer_value = tokens.values.integer implement { name = "mathclassvalue", -- usage = "value", public = true, arguments = "string", actions = function(name) -- return integer_value, classes[name] or ordinary_class context(tostring(classes[name] or ordinary_class)) end } -- used in math-tag: so there we need to make things ord etc to fit within -- mathml local codes = allocate { ordinary = ordinary_class, [ordinary_class] = "ordinary", largeoperator = operator_class, [operator_class] = "largeoperator", binaryoperator = binary_class, [binary_class] = "binaryoperator", relation = relation_class, [relation_class] = "relation", openingsymbol = open_class, [open_class] = "openingsymbol", closingsymbol = close_class, [close_class] = "closingsymbol", punctuation = punctuation_class, [punctuation_class] = "punctuation", middlesymbol = middle_class, [middle_class] = "middlesymbol", } local extensibles = allocate { unknown = 0, l = 1, left = 1, r = 2, right = 2, h = 3, horizontal = 3,-- lr or rl u = 5, up = 4, d = 5, down = 5, v = 6, vertical = 6,-- ud or du m = 7, mixed = 7, } table.setmetatableindex(extensibles,function(t,k) t[k] = 0 return 0 end) local virtualized = allocate { } function mathematics.virtualize(unicode,virtual) local function virtualize(k,v) local c = virtualized[k] if c == v then report_math("character %C is already virtualized to %C",k,v) elseif c then report_math("character %C is already virtualized to %C, ignoring mapping to %C",k,c,v) else virtualized[k] = v end end if type(unicode) == "table" then for k, v in next, unicode do virtualize(k,v) end elseif type(unicode) == "number" and type(virtual) == "number" then virtualize(unicode,virtual) -- else -- error end end mathematics.extensibles = extensibles mathematics.classes = classes mathematics.toengineclass = toengineclass mathematics.classnames = classnames mathematics.codes = codes -----------.accents = codes mathematics.virtualized = virtualized do local setmathcharacter = function(class,family,slot,unicode,mset,dset) if mset and class ~= ordinary_class then setmathcode("global",slot,class,family,unicode) mset = false end if dset and (class == open_class or class == close_class or class == middle_class or class == division_class) then setdelcode("global",slot,family,unicode,0,0) dset = false end return mset, dset end local function report(class,engine,family,unicode,name) local nametype = type(name) if nametype == "string" then report_math("class %a, engine %a, family %a, char %C, name %a",class,engine,family,unicode,name) elseif nametype == "number" then report_math("class %a, engine %a, family %a, char %C, number %U",class,engine,family,unicode,name) else report_math("class %a, engine %a, family %a, char %C",class,engine,family,unicode) end end local f_accent = formatters[ [[\defUmathtopaccent \%s{%X}{%X}{%X}]] ] local f_fixedtopaccent = formatters[ [[\defUmathfixedtopaccent \%s{%X}{%X}{%X}]] ] local f_fixedbotaccent = formatters[ [[\defUmathfixedbotaccent \%s{%X}{%X}{%X}]] ] local f_topaccent = formatters[ [[\defUmathtopaccent \%s{%X}{%X}{%X}]] ] local f_botaccent = formatters[ [[\defUmathbotaccent \%s{%X}{%X}{%X}]] ] local f_over = formatters[ [[\defUdelimiterover \%s{%X}{%X}{%X}]] ] local f_under = formatters[ [[\defUdelimiterunder\%s{%X}{%X}{%X}]] ] local f_fence = formatters[ [[\defUdelimiter \%s{%X}{%X}{%X}]] ] local f_delimiter = formatters[ [[\defUdelimiter \%s{%X}{%X}{%X}]] ] local f_radical = formatters[ [[\defUradical \%s{%X}{%X}]] ] local f_root = formatters[ [[\defUroot \%s{%X}{%X}]] ] local f_char = formatters[ [[\defUmathchar \%s{%X}{%X}{%X}]] ] local texmathchardef = tex.mathchardef local setmathsymbol = function(name,class,engine,family,slot,stretch) -- hex is nicer for tracing if class == accent_class then ctx_sprint(f_topaccent(name,0,family,slot)) elseif class == topaccent_class then ctx_sprint((stretch and f_topaccent or f_fixedtopaccent)(name,0,family,slot)) elseif class == botaccent_class then ctx_sprint((stretch and f_botaccent or f_fixedbotaccent)(name,0,family,slot)) elseif class == over_class then ctx_sprint(f_over(name,0,family,slot)) elseif class == under_class then ctx_sprint(f_under(name,0,family,slot)) elseif class == open_class or class == close_class or class == middle_class then setdelcode("global",slot,family,slot,0,0) ctx_sprint(f_fence(name,engine,family,slot)) elseif class == delimiter_class then setdelcode("global",slot,family,slot,0,0) ctx_sprint(f_delimiter(name,0,family,slot)) elseif class == radical_class then ctx_sprint(f_radical(name,family,slot)) elseif class == root_class then ctx_sprint(f_root(name,family,slot)) elseif texmathchardef then texmathchardef(name,engine,family,slot,"permanent") else -- beware, open/close and other specials should not end up here ctx_sprint(f_char(name,engine,family,slot)) end end function mathematics.define() if trace_defining then logs.startfilelogging(report_math,"math defined from character definitions") end local family = 0 local data = characters.data -- local function remap(first,last) for unicode=utfbyte(first),utfbyte(last) do setmathcode("global",unicode,ordinary_class,family,unicode) end end remap("0","9") remap("A","Z") remap("a","z") -- setdelcode("global",0x2E,0,0,0,0) -- period is special -- for unicode, character in sortedhash(data) do local symbol = character.mathsymbol local mset = true local dset = true if symbol then local other = data[symbol] local class = other.mathclass if class then local engine = toengineclass(class) if trace_defining then report(class,engine,family,unicode,symbol) end mset, dset = setmathcharacter(engine,family,unicode,symbol,mset,dset) end local spec = other.mathspec if spec then for i=1,#spec do local m = spec[i] local class = m.class if class then local engine = toengineclass(class) -- todo: trace mset, dset = setmathcharacter(engine,family,unicode,symbol,mset,dset) end end end end local class = character.mathclass local spec = character.mathspec local name = character.mathname local stretch = character.mathstretch if spec then local done = false if class then if name then report_math("fatal error, conflicting mathclass and mathspec for %C",unicode) os.exit() else class = classes[class] or ordinary_class local engine = toengineclass(class) if trace_defining then report(class,engine,family,unicode) end mset, dset = setmathcharacter(engine,family,unicode,unicode,mset,dset) done = true end end for i=1,#spec do local m = spec[i] local name = m.name local class = m.class or class if class then class = classes[class] or ordinary_class else class = ordinary_class end if class then local engine = toengineclass(class) if name then if trace_defining then report(class,engine,family,unicode,name) end setmathsymbol(name,class,engine,family,unicode,stretch) else name = (class == classes.ordinary or class == classes.digit) and character.adobename -- bad if name and trace_defining then report(class,engine,family,unicode,name) end end if not done then mset, dset = setmathcharacter(engine,family,unicode,m.unicode or unicode,mset,dset) -- see solidus done = true end end end else if class then class = classes[class] or ordinary_class else class = ordinary_class end if name ~= nil then local engine = toengineclass(class) if name == false then if trace_defining then report(class,engine,family,unicode,name) end mset, dset = setmathcharacter(engine,family,unicode,unicode,mset,dset) else -- if not name then -- name = character.contextname -- too dangerous, we loose textslash and a few more -- end if name then if trace_defining then report(class,engine,family,unicode,name) end setmathsymbol(name,class,engine,family,unicode,stretch) else if trace_defining then report(class,engine,family,unicode,character.adobename) end end mset, dset = setmathcharacter(engine,family,unicode,unicode,mset,dset) end elseif class ~= ordinary_class then local engine = toengineclass(class) if trace_defining then report(class,engine,family,unicode,character.adobename) end mset, dset = setmathcharacter(engine,family,unicode,unicode,mset,dset) end end end -- if trace_defining then logs.stopfilelogging() end end end -- needed for mathml analysis -- string with # > 1 are invalid -- we could cache do local lpegmatch = lpeg.match local utf8byte = lpeg.patterns.utf8byte * lpeg.P(-1) -- function somechar(c) -- local b = lpegmatch(utf8byte,c) -- return b and chardata[b] -- end local somechar = table.setmetatableindex(function(t,k) if k then local b = lpegmatch(utf8byte,k) local v = b and chardata[b] or false t[k] = v return v end end) local function utfmathclass(chr, default) local cd = somechar[chr] return cd and cd.mathclass or default or "unknown" end local function utfmathlimop(chr) local cd = somechar[chr] return cd and cd.mathclass == "limop" or false end local function utfmathaccent(chr,default,asked1,asked2) local cd = somechar[chr] if not cd then return default or false end if asked1 and asked1 ~= "" then local mc = cd.mathclass if mc and (mc == asked1 or mc == asked2) then return true end local ms = cd.mathspec if not ms then local mp = cd.mathparent if mp then ms = chardata[mp].mathspec end end if ms then for i=1,#ms do local msi = ms[i] local mc = msi.class if mc and (mc == asked1 or mc == asked2) then return true end end end else local mc = cd.mathclass if mc then return accents[mc] or default or false end local ms = cd.mathspec if ms then for i=1,#ms do local msi = ms[i] local mc = msi.class if mc then return accents[mc] or default or false end end end end return default or false end local function utfmathstretch(chr,default) -- "h", "v", "b", "" local cd = somechar[chr] return cd and cd.mathstretch or default or "" end local function utfmathcommand(chr,default,asked1,asked2) local cd = somechar[chr] if not cd then return default or "" end if asked1 then local mn = cd.mathname local mc = cd.mathclass if mn and mc and (mc == asked1 or mc == asked2) then return mn end local ms = cd.mathspec if not ms then local mp = cd.mathparent if mp then ms = chardata[mp].mathspec end end if ms then for i=1,#ms do local msi = ms[i] local mn = msi.name if mn then local mc = msi.class if mc == asked1 or mc == asked2 then return mn end end end end else local mn = cd.mathname if mn then return mn end local ms = cd.mathspec if ms then for i=1,#ms do local msi = ms[i] local mn = msi.name if mn then return mn end end end end return default or "" end local function utfmathfiller(chr, default) local cd = somechar[chr] local cmd = cd and cd.mathfiller -- or cd.mathname return cmd or default or "" end mathematics.utfmathclass = utfmathclass mathematics.utfmathstretch = utfmathstretch mathematics.utfmathcommand = utfmathcommand mathematics.utfmathfiller = utfmathfiller mathematics.utfmathaccent = utfmathaccent -- interfaced implement { name = "utfmathclass", public = true, actions = { utfmathclass, context }, arguments = "argument" } implement { name = "utfmathstretch", public = true, actions = { utfmathstretch, context }, arguments = "argument" } implement { name = "utfmathcommand", public = true, actions = { utfmathcommand, context }, arguments = "argument" } implement { name = "utfmathfiller", public = true, actions = { utfmathfiller, context }, arguments = "argument" } implement { name = "utfmathcommandabove", public = true, actions = { utfmathcommand, context }, arguments = { "argument", false, "'topaccent'","'over'" } } implement { name = "utfmathcommandbelow", public = true, actions = { utfmathcommand, context }, arguments = { "argument", false, "'botaccent'","'under'" } } implement { name = "utfmathcommandfiller", public = true, actions = { utfmathfiller, context }, arguments = "argument" } -- todo: make this a helper: implement { name = "doifelseutfmathabove", public = true, actions = { utfmathaccent, ctx_doifelsesomething }, arguments = { "argument", false, "'topaccent'", "'over'" } } implement { name = "doifelseutfmathbelow", public = true, actions = { utfmathaccent, ctx_doifelsesomething }, arguments = { "argument", false, "'botaccent'", "'under'" } } implement { name = "doifelseutfmathaccent", public = true, actions = { utfmathaccent, ctx_doifelsesomething }, arguments = "argument", } implement { name = "doifelseutfmathfiller", public = true, actions = { utfmathfiller, ctx_doifelsesomething }, arguments = "argument", } implement { name = "doifelseutfmathlimop", public = true, actions = { utfmathlimop, ctx_doifelsesomething }, arguments = "argument" } end -- helpers -- -- 1: step 1 -- 2: step 2 -- 3: htdp * 1.33^n -- 4: size * 1.33^n -- 5: use lfg function mathematics.big(tfmdata,unicode,n,method) local t = tfmdata.characters local c = t[unicode] if c and n > 0 then if method == 1 or method == 2 or method == 5 then if method == 5 then local b = tfmdata.bigslots if b then n = (n > #b and b[#b]) or b[n] or n end elseif method == 2 then -- large steps n = n * 2 end local next = c.next while next do if n <= 1 then return next else n = n - 1 local tn = t[next].next if tn then next = tn else return next end end end elseif method >= 3 then local size = 1.33^n if method == 4 then size = tfmdata.parameters.size * size else -- if method == 3 then size = (c.height + c.depth) * size end local next = c.next while next do local cn = t[next] if (cn.height + cn.depth) >= size then return next else local tn = cn.next if tn then next = tn else return next end end end end end return unicode end do -- experimental -- local categories = { } -- indexed + hashed -- -- local a_mathcategory = attributes.private("mathcategory") -- -- local function registercategory(category,tag,data) -- always same data for tag -- local c = categories[category] -- if not c then -- c = { } -- categories[category] = c -- end -- local n = c[tag] -- if not n then -- n = #c + 1 -- c[n] = data -- n = n * 1000 + category -- c[tag] = n -- end -- return n -- end -- -- function mathematics.getcategory(n) -- local category = n % 1000 -- return category, categories[category][floor(n/1000)] -- end -- -- mathematics.registercategory = registercategory -- -- function commands.taggedmathfunction(tag,label) -- if label then -- texsetattribute(a_mathcategory,registercategory(1,tag,tag)) -- context.mathlabeltext(tag) -- else -- texsetattribute(a_mathcategory,1) -- context(tag) -- end -- end local categories = { } mathematics.categories = categories local a_mathcategory = attributes.private("mathcategory") local functions = storage.allocate() categories.functions = functions local noffunctions = 1000 -- offset implement { name = "tagmfunctiontxt", arguments = { "string", "conditional" }, actions = function(tag,apply) local delta = apply and 1000 or 0 texsetattribute(a_mathcategory,1000 + delta) end } implement { name = "tagmfunctionlab", arguments = { "string", "conditional" }, actions = function(tag,apply) local delta = apply and 1000 or 0 local n = functions[tag] if not n then noffunctions = noffunctions + 1 functions[noffunctions] = tag functions[tag] = noffunctions texsetattribute(a_mathcategory,noffunctions + delta) else texsetattribute(a_mathcategory,n + delta) end end } end do local list function mathematics.resetattributes() if not list then list = { } for k, v in next, attributes.numbers do if find(k,"^math") then list[#list+1] = v end end end for i=1,#list do texsetattribute(list[i],unsetvalue) end end end implement { name = "resetmathattributes", public = true, protected = true, actions = mathematics.resetattributes } -- weird to do this here but it's a side affect of math anyway implement { name = "enableasciimode", onlyonce = true, actions = resolvers.macros.enablecomment, }