if not modules then modules = { } end modules ['math-tag'] = { 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" } -- todo: have a local list with local tags that then get appended -- todo: use tex.getmathcodes (no table) -- todo: add more spacing details + check text stuff for latest additions -- todo: some more font related cleanup + adaption to new scaling -- todo: tracing -- todo: maybe use lpeg matchers -- todo: prime -- todo: middle in fraction local find, match, gsub = string.find, string.match, string.gsub local insert, remove, concat, setmetatableindex = table.insert, table.remove, table.concat, table.setmetatableindex local attributes = attributes local nodes = nodes local nuts = nodes.nuts local tonut = nuts.tonut local getchar = nuts.getchar local getnext = nuts.getnext local getprev = nuts.getprev local getdata = nuts.getdata local getlist = nuts.getlist local getfield = nuts.getfield local getdisc = nuts.getdisc local getattr = nuts.getattr local setattr = nuts.setattr local getwidth = nuts.getwidth local getoptions = nuts.getoptions local getclass = nuts.getclass local getprop = nuts.getprop local getcharspec = nuts.getcharspec local getchardict = nuts.getchardict local getnumerator = nuts.getnumerator local getdenominator = nuts.getdenominator local getdelimiter = nuts.getdelimiter local getleftdelimiter = nuts.getleftdelimiter local getrightdelimiter = nuts.getrightdelimiter local gettopdelimiter = nuts.gettopdelimiter local getbottomdelimiter = nuts.getbottomdelimiter local getdegree = nuts.getdegree local gettop = nuts.gettop local getbottom = nuts.getbottom local getchoice = nuts.getchoice local getnucleus = nuts.getnucleus local setattributes = nuts.setattributes local nextnode = nuts.traversers.node local nodecodes = nodes.nodecodes local accentcodes = nodes.accentcodes local fencecodes = nodes.fencecodes local fractioncodes = nodes.fractioncodes local kerncodes = nodes.kerncodes local noad_code = nodecodes.noad local accent_code = nodecodes.accent local radical_code = nodecodes.radical local fraction_code = nodecodes.fraction local subbox_code = nodecodes.subbox local submlist_code = nodecodes.submlist local mathchar_code = nodecodes.mathchar local mathtextchar_code = nodecodes.mathtextchar local delimiter_code = nodecodes.delimiter local style_code = nodecodes.style local choice_code = nodecodes.choice local fence_code = nodecodes.fence local hlist_code = nodecodes.hlist local vlist_code = nodecodes.vlist local glyph_code = nodecodes.glyph local disc_code = nodecodes.disc local glue_code = nodecodes.glue local kern_code = nodecodes.kern local rule_code = nodecodes.rule local math_code = nodecodes.math local fixedtopaccent_code = accentcodes.fixedtop local fixedbottomaccent_code = accentcodes.fixedbottom local fixedbothaccent_code = accentcodes.fixedboth local leftfence_code = fencecodes.left local middlefence_code = fencecodes.middle local rightfence_code = fencecodes.right local operatorfence_code = fencecodes.operator local atop_code = fractioncodes.atop local above_code = fractioncodes.above local fontkern_code = kerncodes.fontkern local italiccorrection_code = kerncodes.italiccorrection local hextensible_code = nodes.radicalcodes.hextensible local delimited_code = nodes.radicalcodes.delimited ----- delimiterover_code = nodes.radicalcodes.delimiterover ----- delimiterunder_code = nodes.radicalcodes.delimiterunder ----- overdelimiter_code = nodes.radicalcodes.overdelimiter ----- underdelimiter_code = nodes.radicalcodes.underdelimiter local lextensible_code = nodes.listcodes.hextensible local gextensible_code = nodes.glyphcodes.extensible local a_tagged = attributes.private('tagged') local a_mathcategory = attributes.private('mathcategory') local a_mathstack = attributes.private('mathstack') local a_mathmode = attributes.private('mathmode') local a_mathfamily = attributes.private('mathfamily') local a_mathdomain = attributes.private('mathdomain') local c_mathblobnesting = tex.iscount("currentmathblobnesting") local c_mathblob = tex.iscount("currentmathblob") local processnoads = noads.process local getintegervalue = tex.getintegervalue local getmacro = tokens.getters.macro local tags = structures.tags local start_tagged = tags.start local restart_tagged = tags.restart local push_tagged = tags.push local pop_tagged = tags.pop local stop_tagged = tags.stop local taglist = tags.taglist ----- chardata = characters.data local getmathcodes = tex.getmathcodes local classes = mathematics.classes local classtotag = { [classes.ordinary] = "mi", [classes.variable] = "mi", [classes.imaginary] = "mi", [classes.differential] = "mi", [classes.exponential] = "mi", [classes.digit] = "mn", [classes.implication] = "mo", [classes.ghost] = "mo",--always? -- [classes.relation] = "mo", -- [classes.binary] = "mo", -- [classes.punctuation] = "mc", } local fromunicode16 = fonts.mappings.fromunicode16 local fontcharacters = fonts.hashes.characters local report_tags = logs.reporter("structure","tags") local tagging = { -- mfenced = true, mfenced = false, } directives.register("structures.tags.math.mfenced", function(v) tagging.mfenced = v end) mathematics.tagging = tagging local process, processsubsup do local noadoptioncodes = tex.noadoptioncodes local continuation_code = noadoptioncodes.continuation local continuationhead_code = noadoptioncodes.continuationhead local continuationkernel_code = noadoptioncodes.continuationkernel local indexedsuperscript_code = noadoptioncodes.indexedsuperscript local indexedsubscript_code = noadoptioncodes.indexedsubscript local indexedsuperprescript_code = noadoptioncodes.indexedsuperprescript local indexedsubprescript_code = noadoptioncodes.indexedsubprescript local limits_code = noadoptioncodes.limits -- x[prime + sup] : x sup prime -- x[prime + sub] : x sub prime -- x[prime + sup + sub] : x sub sup prime -- x[prime + sup + sub] : x sub prime -- -- x[prime] : x prime -- x[sup] : x sup -- x[sub] : x sub -- x[sup + sub] : x sub sup local t_prime = { script = "prime" } -- etc, todo: set on main table local function simple(start,nucleus,prime,sup,sub,options,continuation,limits) if prime then setattr(start,a_tagged,start_tagged("msup", { prime = true })) start_tagged("mrow") end if sub then if sup then local subindexed = ((options & indexedsubscript_code ) > 0) or nil local supindexed = ((options & indexedsuperscript_code) > 0) or nil if continuation then continuation.subindexed = subindexed continuation.supindexed = supindexed continuation.limits = limits else continuation = { subindexed = subindexed, supindexed = supindexed, limits = limits, } end setattr(start,a_tagged,start_tagged("msubsup",continuation)) process(nucleus) start_tagged("mrow", { script = "sub" }) process(sub) stop_tagged() start_tagged("mrow", { script = "sup" }) process(sup) stop_tagged() stop_tagged() else local subindexed = ((options & indexedsubscript_code) > 0) or nil if continuation then continuation.subindexed = subindexed continuation.limits = limits else continuation = { subindexed = subindexed, limits = limits, } end setattr(start,a_tagged,start_tagged("msub",continuation)) process(nucleus) start_tagged("mrow", { script = "sub" }) process(sub) stop_tagged() stop_tagged() end elseif sup then local supindexed = ((options & indexedsuperscript_code) > 0) or nil if continuation then continuation.supindexed = supindexed continuation.limits = limits else continuation = { supindexed = supindexed, limits = limits, } end setattr(start,a_tagged,start_tagged("msup",continuation)) process(nucleus) start_tagged("mrow", { script = "sup" }) process(sup) stop_tagged() stop_tagged() else process(nucleus) end if prime then stop_tagged() start_tagged("mrow", { script = "prime" }) process(prime) stop_tagged() stop_tagged() end end local function complex(start,nucleus,prime,sup,sub,presup,presub,options,continuation,limits) if prime then setattr(start,a_tagged,start_tagged("msup", { prime = true })) start_tagged("mrow") end -- -- makes little sense -- if continuation then -- continuation.limits = limits -- else -- continuation = { limits = limits } -- end start_tagged("mmultiscripts", continuation) process(nucleus) -- if prime then -- start_tagged("mrow", { script = "prime" }) -- process(prime) -- stop_tagged() -- end if sup then start_tagged("mrow", { script = "sup" }) -- , indexed = ((options & indexedsuperscript_code) > 0) and "true" or nil }) process(sup) stop_tagged() end if sub then start_tagged("mrow", { script = "sub" }) -- , indexed = ((options & indexedsubscript_code) > 0) and "true" or nil }) process(sub) stop_tagged() end if presup then start_tagged("mrow", { script = "presup" }) -- , indexed = ((options & indexedsuperprescript_code) > 0) and "true" or nil }) process(presup) stop_tagged() end if presub then start_tagged("mrow", { script = "presub" }) -- , indexed = ((options & indexedsubprescript_code) > 0) and "true" or nil }) process(presub) stop_tagged() end stop_tagged() if prime then stop_tagged() start_tagged("mrow", { script = "prime" }) process(prime) stop_tagged() stop_tagged() end end local multiprime = true directives.register("structures.tags.math.multiprime", function(v) multiprime = v end) processsubsup = function(start) -- At some point we might need to add an attribute signaling the -- super- and subscripts because TeX and MathML use a different -- order. The mrows are needed to keep mn's separated. local nucleus, prime, sup, sub, presup, presub = getnucleus(start,true) local options = getoptions(start) or 0 local limits = (options & limits_code) > 0 local continuation = false local c = (options & continuation_code) > 0 local h = (options & continuationhead_code) > 0 local k = (options & continuationkernel_code) > 0 local continuation = ((c or h or k) and { continuation = { next = c, head = h, kernel = k} }) or nil -- if presup or presub or ( sup and prime) then if (presup or presub) or (multiprime and sup and prime) then complex(start,nucleus,prime,sup,sub,presup,presub,options,continuation,limits) else simple(start,nucleus,prime,sup,sub,options,continuation,limits) end end end -- todo: check function here and keep attribute the same -- todo: variants -> original local actionstack = { } local fencesstack = { } -- glyph nodes and such can happen in under and over stuff local function getunicode(n) -- instead of getchar local char, font = getcharspec(n) local data = fontcharacters[font][char] return data.unicode or char -- can be a table but unlikely for math characters end ------------------- local content = { } local found = false content[mathchar_code] = function() found = true end local function hascontent(head) found = false processnoads(head,content,"content") return found end -------------------- -- todo: use properties -- local function showtag(n,id,old) -- local attr = getattr(n,a_tagged) -- local curr = tags.current() -- report_tags("%s, node %s, attr %s:%s (%s), top %s (%s)", -- old and "before" or "after ", -- nodecodes[id], -- getattrlist(n), -- attr or "?",attr and taglist[attr].tagname or "?", -- curr or "?",curr and taglist[curr].tagname or "?" -- ) -- end -- I need to bring this in sync with new or removed mathml 3, not that there has -- been many changes. It will happen in sync with other mathml updates in context -- where we also keep adapting to a cycling between either or not support in -- browsers, the come-and-go of alternatives like ascii math and mathjax. It's the -- web and browser support that drives this, not tex and its community. So, maybe -- I'll add some more detail here, nto that it matters much in the long run where we -- only focus on structure and let the engine deal with the details. Another reason -- to update this is that we can add some tracing (lmtx only). -- This has been working ok for quite but in 2023 it's time to have a look at it -- again and see to what extend we need to adapt to new features. Around the time -- PG's Panopticom was put on youtube. local chardata = characters.data local mathnames = mathematics.dictionaries.names local mathgroups = mathematics.dictionaries.groups local mathclasses = mathematics.classes local everygroup = mathematics.dictionaries.names.everygroup local variable_class = classes.variable local function getproperties(n,class) local props, group, index, font, char = getchardict(n) if group == everygroup then -- should be done at the lua end by defining a char bound group code local c = chardata[char] if c then local g = c.mathgroup if not g then local s = c.mathspec if s then g = s[1].group end if g then group = mathnames[g] or group end end end end if group or class then local swapped = getprop(n,"swappedclass") if swapped then class = swapped end return { mathclass = mathclasses[class] or class, mathgroup = mathgroups[group], mathindex = index, -- mathfont = font, mathcharacter = char, } end end -- todo: why seen twice -- todo: get rid of detail do local level = -1 local trace = false trackers.register("export.trace.math", function(v) trace = v end) local function show(n,id,where) -- inspect(getattr(n,a_tagged)) -- inspect(taglist) local a = getattr(n,a_tagged) local s = a and taglist[a] if s then s = s.taglist end report_tags("%w %s : %S : %s : %s", level, where, nodecodes[id], a or "?", s and concat(s," ", 3) or "untagged" ) end -- Beware, the first node in list is the actual list so we definitely need to nest. -- This approach is a hack, maybe I'll make a proper nesting feature to deal with -- this at another level. Here we just fake structure by enforcing the inner one. -- todo: have a local list with local tags that then get appended local function runner(td,nd,text,list) -- quite inefficient local cache = { } -- we can have nested unboxed mess so best local to runner local keep = nil -- local keep = { } -- in case we might need to move keep outside for n, id, subtype in nextnode, list do local mth = id == math_code and subtype if mth == 0 then -- begin in line / nested math box like stackers -- insert(keep,text) keep = text text = start_tagged("mrow") -- common = common + 1 end local aa = getattr(n,a_tagged) if aa then -- here we could intercept formulacaption and formulanumber local ac = cache[aa] if not ac then local tagspec = taglist[aa] local tagdata = tagspec.taglist local common = 0 for i=1,nd do if td[i] == tagdata[i] then common = common + 1 else break end end common = common + 1 local extra = #tagdata if common <= extra then if trace then show(n,id," ") end for i=common,extra do -- don't we loose properties here? ac = restart_tagged(tagdata[i]) end for i=common,extra do stop_tagged() end else ac = text end cache[aa] = ac end setattr(n,a_tagged,ac) else setattr(n,a_tagged,text) end if id == hlist_code or id == vlist_code then runner(td,nd,text,getlist(n)) -- elseif id == glyph_code then elseif id == disc_code then -- this should not be needed local pre, post, replace = getdisc(n) if pre then runner(td,nd,text,pre) end if post then runner(td,nd,text,post) end if replace then runner(td,nd,text,replace) end end if mth == 1 then -- end in line stop_tagged() -- text = remove(keep) text = keep -- common = common - 1 end end end local function relocate(start,attr,tag,specification) -- we can now end up with nexted "math" local detail = specification.detail local text = start_tagged(tag, detail and { detail = detail } or nil) setattr(start,a_tagged,text) local list = getlist(start) if list then local tagdata = specification.taglist runner(tagdata,#tagdata,text,list) end stop_tagged() end local function makeintotext(start) setattr(start,a_tagged,start_tagged("mtext")) stop_tagged() end process = function(start) -- we cannot use the processor as we have no finalizers (yet) local mtexttag = nil level = level + 1 for start, id, subtype in nextnode, start do -- current if trace then show(start,id,"+") end if id == glyph_code or id == disc_code then if not mtexttag then mtexttag = start_tagged("mtext") end setattr(start,a_tagged,mtexttag) elseif mtexttag and id == kern_code and (subtype == fontkern_code or subtype == italiccorrection_code) then setattr(start,a_tagged,mtexttag) elseif id == rule_code then -- setattr(start,a_tagged,start_tagged("ignore")) -- stop_tagged() else if mtexttag then stop_tagged() mtexttag = nil end if id == mathchar_code then -- local char = getchar(start) -- not used local properties = getproperties(start,subtype) local tag = classtotag[subtype] or "mo" properties.mathstack = getattr(start,a_mathstack) properties.mathcategory = getattr(start,a_mathcategory) setattr(start,a_tagged,start_tagged(tag,properties)) stop_tagged() if trace then show(start,id,"C") end level = level - 1 break -- okay? elseif id == mathtextchar_code then -- or id == glyph_code -- check for code local a = getattr(start,a_mathcategory) if a then -- cache table setattr(start,a_tagged,start_tagged("ms",{ mathcategory = a })) -- mtext else setattr(start,a_tagged,start_tagged("ms")) -- mtext end stop_tagged() if trace then show(start,id,"T") end level = level - 1 break elseif id == delimiter_code then -- check for code local properties = getproperties(start,subtype) properties.delimiter = "true" -- inspect(properties) setattr(start,a_tagged,start_tagged("mo",properties)) stop_tagged() if trace then show(start,id,"D") end level = level - 1 break elseif id == style_code then -- has a next elseif id == noad_code then processsubsup(start) elseif id == subbox_code or id == hlist_code or id == vlist_code then -- keep an eye on subbox_code and see what ends up in there -- a hlist can be a nested result (mlist_to_hlist) local attr = getattr(start,a_tagged) if attr then local specification = taglist[attr] if specification then local tag = specification.tagname -- if tag == "formulacaption" then -- -- can this happen, skip -- else if tag == "mstacker" then -- this still happens local list = getlist(start) if list then process(list) end else if tag ~= "math" and tag ~= "mtable" and tag ~= "mtd" and tag ~= "mstackertop" and tag ~= "mstackermid" and tag ~= "mstackerbot" -- and tag ~= "formulacaption" -- and tag ~= "formulanumber" then tag = "mtext" end relocate(start,attr,tag,specification) end else makeintotext(start) -- last resort gamble end else makeintotext(start) -- last resort gamble end elseif id == submlist_code then -- normally a hbox local list = getlist(start) if list then local attr = getattr(start,a_tagged) local last = attr and taglist[attr] if last then local tag = last.tagname local detail = last.detail if tag == "munit" then setattr(start,a_tagged,start_tagged("mrow", { mathunit = detail })) process(list) stop_tagged() elseif tag == "mfunction" then setattr(start,a_tagged,start_tagged("mrow", { mathfunction = detail, mathcategory = getattr(start,a_mathcategory), mathstack = getattr(start,a_mathstack), })) process(list) stop_tagged() elseif tag == "mstacker" then setattr(start,a_tagged,restart_tagged(attr)) -- so we just reuse the attribute process(list) stop_tagged() elseif tag == "mfunctionstack" then setattr(start,a_tagged,start_tagged("mrow", { mathfunctionstack = detail, mathstack = getattr(start,a_mathstack), })) process(list) stop_tagged() elseif tag == "mfractionstack" then setattr(start,a_tagged,start_tagged("mrow", { mathfractionstack = detail, -- mathstack = getattr(start,a_mathstack), })) process(list) stop_tagged() elseif tag == "mdelimitedstack" then setattr(start,a_tagged,start_tagged("mrow", { mathdelimitedstack = detail, -- mathstack = getattr(start,a_mathstack), })) process(list) stop_tagged() elseif tag == "mdigits" then setattr(start,a_tagged,start_tagged("mrow", { mathdigits = detail or "unknown", })) process(list) stop_tagged() else setattr(start,a_tagged,start_tagged("mrow")) process(list) stop_tagged() end else -- never happens, we're always document setattr(start,a_tagged,start_tagged("mrow")) process(list) stop_tagged() end end elseif id == fraction_code then -- -- if middle then we have a stacker! -- local num = getnumerator(start) local denom = getdenominator(start) local left = getleftdelimiter(start) local right = getrightdelimiter(start) local middle = getdelimiter(start) -- todo get them all in one go if left then setattr(left,a_tagged,start_tagged("mo")) process(left) stop_tagged() end setattr(start,a_tagged,start_tagged("mfrac", { mathfractionrule = (subtype == atop_code or subtype == above_code) and "no" or "yes", })) process(num) process(denom) stop_tagged() if middle then setattr(middle,a_tagged,start_tagged("ignore")) stop_tagged() end if right then setattr(right,a_tagged,start_tagged("mo")) process(right) stop_tagged() end elseif id == choice_code then local display = getchoice(start,1) local text = getchoice(start,2) local script = getchoice(start,3) local scriptscript = getchoice(start,4) if display then process(display) end if text then process(text) end if script then process(script) end if scriptscript then process(scriptscript) end elseif id == fence_code then local delimiter = getdelimiter(start) local mfenced = tagging.mfenced local mathcategory = getattr(start,a_mathcategory) if subtype == leftfence_code then local properties = { mathcategory = mathcategory } insert(fencesstack,properties) setattr(start,a_tagged,start_tagged("mfenced",properties)) -- needs checking if delimiter then if mfenced then start_tagged("ignore") end local chr = getchar(delimiter) if chr ~= 0 then properties.left = chr end process(delimiter) if mfenced then stop_tagged() end end start_tagged("mrow") -- begin of subsequence elseif subtype == middlefence_code then stop_tagged() -- end of subsequence if delimiter then if mfenced then start_tagged("ignore") end local top = fencesstack[#fencesstack] local chr = getchar(delimiter) if chr ~= 0 then local mid = top.middle if mid then mid[#mid+1] = chr else top.middle = { chr } end end process(delimiter) if mfenced then stop_tagged() end end start_tagged("mrow") -- begin of subsequence elseif subtype == rightfence_code then stop_tagged() -- end of subsequence local properties = remove(fencesstack) if not properties then report_tags("missing right fence") properties = { } end if delimiter then if mfenced then start_tagged("ignore") end local chr = getchar(delimiter) if chr ~= 0 then properties.right = chr end process(delimiter) if mfenced then stop_tagged() end end stop_tagged() elseif subtype == operatorfence_code then local properties = { operator = true, mathcategory = mathcategory, } local top = gettopdelimiter(start) local bot = getbottomdelimiter(start) insert(fencesstack,properties) setattr(start,a_tagged,start_tagged("mrow",properties)) if top then if bot then start_tagged("msubsup") else start_tagged("msup") end elseif bot then start_tagged("msub") else start_tagged("mrow",properties) end if delimiter then start_tagged("mrow") local chr = getchar(delimiter) if chr ~= 0 then properties.left = chr end process(delimiter) stop_tagged() else -- error end if top or bot then if top then start_tagged("mrow", { script = "sup" }) process(top) stop_tagged() end if bot then start_tagged("mrow", { script = "sub" }) process(bot) stop_tagged() end end stop_tagged() start_tagged("mrow") -- begin of subsequence else -- no fence local a = getattr(start,a_mathcategory) local properties = a and { mathcategory = a } start_tagged("mrow",properties) -- begin of subsequence if delimiter then if mfenced then start_tagged("ignore") end process(delimiter) if mfenced then stop_tagged() end end stop_tagged() end elseif id == radical_code then local left = getleftdelimiter(start) local right = getrightdelimiter(start) if subtype == hextensible_code then -- eventually we have no radical but just some box if left then start_tagged("mo") process(left) stop_tagged() end elseif subtype == delimited_code then start_tagged("mdelimited",properties) if left then local properties = getproperties(left,subtype) or { } properties.delimiterlocation = "left" setattr(left,a_tagged,start_tagged("mo", properties)) stop_tagged() end if right then local properties = getproperties(right,subtype) or { } properties.delimiterlocation = "right" setattr(right,a_tagged,start_tagged("mo",properties)) stop_tagged() end processsubsup(start) stop_tagged() else local degree = getdegree(start) if left then start_tagged("ignore") process(left) -- root symbol, ignored stop_tagged() end if right then start_tagged("ignore") process(right) -- actuarian symbol, ignored stop_tagged() end if degree and hascontent(degree) then setattr(start,a_tagged,start_tagged("mroot")) processsubsup(start) process(degree) stop_tagged() else setattr(start,a_tagged,start_tagged("msqrt")) processsubsup(start) stop_tagged() end end elseif id == accent_code then -- maybe tag the 'mo' so that we can reorder but we only use -- under and over anyway local topaccent = gettop(start) local bottomaccent = getbottom(start) local middleaccent = getdelimiter(start) local mathcategory = getattr(start,a_mathcategory) local topfixed = subtype == fixedbothaccent_code or subtype == fixedtopaccent_code local bottomfixed = subtype == fixedbothaccent_code or subtype == fixedbottomaccent_code if bottomaccent then if topaccent then setattr(start,a_tagged,start_tagged("munderover", { accent = true, top = getunicode(topaccent), bottom = getunicode(bottomaccent), topfixed = topfixed, bottomfixed = bottomfixed, mathcategory = mathcategory, })) if topfixed then topfixed = nil else topfixed = { delimiter = "true" } end if bottomfixed then bottomfixed = nil else bottomfixed = { delimiter = "true" } end processsubsup(start) setattr(bottomaccent,a_tagged,start_tagged("mo",bottomfixed)) stop_tagged() setattr(topaccent,a_tagged,start_tagged("mo",topfixed)) stop_tagged() stop_tagged() else setattr(start,a_tagged,start_tagged("munder", { accent = true, bottom = getunicode(bottomaccent), bottomfixed = bottomfixed, mathcategory = mathcategory, })) if bottomfixed then bottomfixed = nil else bottomfixed = { delimiter = "true" } end processsubsup(start) setattr(bottomaccent,a_tagged,start_tagged("mo",bottomfixed)) stop_tagged() stop_tagged() end elseif topaccent then setattr(start,a_tagged,start_tagged("mover", { accent = true, top = getunicode(topaccent), topfixed = topfixed, mathcategory = mathcategory, })) if topfixed then topfixed = nil else topfixed = { delimiter = "true" } end processsubsup(start) setattr(topaccent,a_tagged,start_tagged("mo",topfixed)) stop_tagged() stop_tagged() else processsubsup(start) end elseif id == glue_code then -- before processing, so other intermathglue is not tagged local em = fonts.hashes.emwidths[nuts.getfont(start)] local wd = getwidth(start) if em and wd then setattr(start,a_tagged,start_tagged("mspace",{ emfactor = wd/em })) stop_tagged() end else -- rule boundary end end if trace then show(start,id,"-") end end if mtexttag then stop_tagged() end level = level - 1 end end local standalone = false directives.register("structures.tags.math.standalone", function(v) standalone = v end) function noads.handlers.tags(head,style,penalties,beginclass,endclass,level,style) if not context.trialtypesetting() then local a = tex.getattribute(a_mathfamily) -- brrr start_tagged("math", { mode = (getattr(head,a_mathmode) == 1) and "display" or "inline", standalone = standalone, family = mathematics.familyname(a), style = style, input = mathematics.lastinput, blob = getintegervalue(c_mathblob), language = getmacro("currentlanguage"), domain = mathematics.getdomainname(tonumber(getattr(head,a_mathdomain))), }) setattr(head,a_tagged,start_tagged("mrow")) if trace then report_tags("start math sweep at level %i",level) end process(head) if trace then report_tags("stop math sweep at level %i",level) end stop_tagged() stop_tagged() end mathematics.lastinput= nil end do -- This one is meant for tracing (in m4all/m4mbo where it complements some other -- tracing) but it actually can also replace the embedding feature although that -- one might be better when we have more complex code with dependencies outside -- the blob. I'll deal with that when it's needed (trivial). The current -- interface is rather minimalistic. local enabled = false local export = false local warned = false local shared = { } local orders = { } local hashed = { } local blobdone = setmetatableindex("table") local trace_blobs = false local report_blob = logs.reporter("math blob") directives.register("structures.tags.math.blobs", function(v) trace_blobs = v end) local function register(order,data) if not data then data = "" end local hash = sha2.HEX256(data) -- maybe direct local index = hashed[hash] if index then if trace_blobs then report_blob("known, order %i, index %i, data: %s",order,index,gsub(shared[index].strip,"%s+","")) end else index = #shared + 1 hashed[hash] = index shared[index] = { data = data, strip = xml.mml.stripped(data), -- we could delay this as it often only happens once } if trace_blobs then report_blob("register, order %i, index %i, data: %s",order,index,gsub(shared[index].strip,"%s+","")) end end orders[order] = index return index end function mathematics.enablecollecting() if structures.tags.enabled() then if not enabled then nodes.tasks.enableaction("math","noads.handlers.export") end enabled = true export = structures.tags.localexport elseif not warned then report_tags("math collecting only works when tagging is enabled") warned = true end end function mathematics.disablecollecting() enabled = false export = false end local function getmathblob(purpose,order) local index = orders[order] if index then local entry = shared[index] if trace_blobs then report_blob("get math data, purpose %a, order %i, index %i, data: %s",purpose,order,index,gsub(entry.strip,"%s+", "")) end return entry.strip end end local function gettextblob(purpose,language,order) if not order then order = language language = "en" end local index = orders[order] if index then local entry = shared[index] local data = entry and entry[language] if not data then -- when no labels we actually now duplicate but so be it local d = entry.data if d and d ~= "" then data = xml.mml.verbose(d,language) entry[language] = data else entry[language] = "" end end if trace_blobs then report_blob("get text data, purpose %a, order %i, index %i, data: %s",purpose,order,index,gsub(entry.strip,"%s+", "")) end return data end end local function getblobindex(purpose,order) local index = orders[order] or 0 if trace_blobs then report_blob("get math order, purpose %a, order %i, index %i",purpose,order,index) end return index end local function getblobmapping(purpose,order) local index = orders[order] or 0 local blobs = job.variables.collected.mathblobs local mapping = blobs and blobs[index] or 0 if trace_blobs then report_blob("get math mapping, purpose %a, order %i, index %i, mapping %04X",purpose,order,index,mapping) end return index end local function markblobindexdone(language,n) blobdone[language][n] = true end mathematics.getmathblob = getmathblob mathematics.gettextblob = gettextblob mathematics.getblobindex = getblobindex mathematics.markblobindexdone = markblobindexdone local integer_value = tokens.values.integer local boolean_value = tokens.values.boolean local implement = interfaces.implement local context = context local ctxescaped = context.ctxescaped implement { name = "getmathmathblob", protected = true, public = true, arguments = "integer", actions = function(n) context(getmathblob("tex",n) or "") end } implement { name = "getmathtextblob", protected = true, public = true, arguments = { "string", "integer" }, actions = function(language,n) ctxescaped(gettextblob("tex",language,n) or "") end } implement { name = "getmathblobindex", public = true, usage = "value", arguments = "integer", actions = function(n) return integer_value, getblobindex("tex",n) end } implement { name = "getmathblobmapping", public = true, usage = "value", arguments = "integer", actions = function(n) return integer_value, getblobmapping("tex",n) end } implement { name = "lastblobindex", -- kind of obsolete public = true, usage = "value", actions = function() return integer_value, #shared end } implement { name = "markblobindexdone", protected = true, public = true, arguments = { "string", "integer" }, actions = markblobindexdone } implement { name = "ifblobindexdone", public = true, usage = "condition", arguments = { "string", "integer" }, actions = function(language,n) return boolean_value, blobdone[language][n] end } implement { name = "enablecollectingmath", -- public = true, protected = true, actions = mathematics.enablecollecting } implement { name = "disablecollectingmath", -- public = true, protected = true, actions = mathematics.disablecollecting } implement { name = "startcollectingmath", -- obsolete -- public = true, protected = true, actions = mathematics.enablecollecting } implement { name = "stopcollectingmath", -- obsolete -- public = true, protected = true, actions = function() end } -- for now here .. will become a proper lpeg local toascii do local utfbyte = utf.byte local gsub, format, find = string.gsub, string.format, string.find local clean = setmetatableindex(function(t,k) local n = utfbyte(k) if n > 127 then n = format("&#x%X;",n) else n = false end t[k] = n return n end) local pattern = utf8.charpattern toascii = function(data) return (gsub(data,pattern,clean)) end end implement { name = "processcollectedmath", -- public = true, protected = true, arguments = "4 strings", actions = function(filename,buffername,n,option) local blob = n and tonumber(n) or getintegervalue(c_mathblob) local data = getmathblob("collect",blob) or "" if option == "ascii" then data = toascii(data) end if filename and filename ~= "" then io.savedata(filename,data) elseif buffername then -- always something buffers.assign(buffername == interfaces.variables.yes and "" or buffername,data) else return data end end } implement { name = "collectedmath", usage = "value", protected = true, public = true, actions = function(what) if what == "value" then return integer_value, #shared else context(getmathblob("collect",tokens.scanners.integer()) or "") end end } local a_mathblob = attributes.private('mathblob') function noads.handlers.export(head) if export then -- nuts.show(head) local nesting = getintegervalue(c_mathblobnesting) local order = getintegervalue(c_mathblob) if nesting == 1 then if trace_blobs then report_blob("export blob, order %i, level %i",order,nesting) end local blob = export(head,"math") if blob then -- something can be wrong at a page break: todo blob = string.gsub(blob,"^(.-