if not modules then modules = { } end modules ['math-noa'] = { version = 1.001, optimize = true, 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" } -- beware: this is experimental code and there will be a more generic (attribute value -- driven) interface too but for the moment this is ok (sometime in 2015-2016 i will -- start cleaning up as by then the bigger picture is clear and code has been used for -- years; the main handlers will get some extensions) -- -- we will also make dedicated processors (faster) -- -- beware: names will change as we wil make noads.xxx.handler i.e. xxx -- subnamespaces -- 20D6 -> 2190 -- 20D7 -> 2192 -- todo: most is mathchar_code so we can have simple dedicated loops -- nota bene: uunderdelimiter uoverdelimiter etc are radicals (we have 5 types) local next, tonumber = next, tonumber local utfchar, utfbyte = utf.char, utf.byte local formatters, gmatch = string.formatters, string.gmatch local sortedhash = table.sortedhash local insert, remove = table.insert, table.remove local div, round = math.div, math.round local bor, band = bit32.bor, bit32.band local fonts = fonts local nodes = nodes local node = node local mathematics = mathematics local context = context local otf = fonts.handlers.otf local otffeatures = fonts.constructors.features.otf local registerotffeature = otffeatures.register local privateattribute = attributes.private local registertracker = trackers.register local registerdirective = directives.register local logreporter = logs.reporter local setmetatableindex = table.setmetatableindex local colortracers = nodes.tracers.colors local trace_remapping = false registertracker("math.remapping", function(v) trace_remapping = v end) local trace_processing = false registertracker("math.processing", function(v) trace_processing = v end) local trace_analyzing = false registertracker("math.analyzing", function(v) trace_analyzing = v end) local trace_normalizing = false registertracker("math.normalizing", function(v) trace_normalizing = v end) local trace_collapsing = false registertracker("math.collapsing", function(v) trace_collapsing = v end) local trace_fixing = false registertracker("math.fixing", function(v) trace_fixing = v end) local trace_patching = false registertracker("math.patching", function(v) trace_patching = v end) local trace_goodies = false registertracker("math.goodies", function(v) trace_goodies = v end) local trace_variants = false registertracker("math.variants", function(v) trace_variants = v end) local trace_alternates = false registertracker("math.alternates", function(v) trace_alternates = v end) local trace_italics = false registertracker("math.italics", function(v) trace_italics = v end) local trace_kernpairs = false registertracker("math.kernpairs", function(v) trace_kernpairs = v end) local trace_domains = false registertracker("math.domains", function(v) trace_domains = v end) local trace_families = false registertracker("math.families", function(v) trace_families = v end) local trace_fences = false registertracker("math.fences", function(v) trace_fences = v end) local trace_unstacking = false registertracker("math.unstack", function(v) trace_unstacking = v end) local check_coverage = true registerdirective("math.checkcoverage", function(v) check_coverage = v end) local report_processing = logreporter("mathematics","processing") local report_remapping = logreporter("mathematics","remapping") local report_normalizing = logreporter("mathematics","normalizing") local report_collapsing = logreporter("mathematics","collapsing") local report_fixing = logreporter("mathematics","fixing") local report_patching = logreporter("mathematics","patching") local report_goodies = logreporter("mathematics","goodies") local report_variants = logreporter("mathematics","variants") local report_alternates = logreporter("mathematics","alternates") local report_italics = logreporter("mathematics","italics") local report_kernpairs = logreporter("mathematics","kernpairs") local report_domains = logreporter("mathematics","domains") local report_families = logreporter("mathematics","families") local report_fences = logreporter("mathematics","fences") local report_unstacking = logreporter("mathematics","unstack") local a_mathrendering = privateattribute("mathrendering") local a_exportstatus = privateattribute("exportstatus") local nuts = nodes.nuts local nodepool = nuts.pool local tonut = nuts.tonut local nutstring = nuts.tostring local setfield = nuts.setfield local setlink = nuts.setlink local setlist = nuts.setlist local setnext = nuts.setnext local setprev = nuts.setprev local setchar = nuts.setchar local setfam = nuts.setfam local setsubtype = nuts.setsubtype local setattr = nuts.setattr local setattrlist = nuts.setattrlist local setwidth = nuts.setwidth local setheight = nuts.setheight local setdepth = nuts.setdepth local getfield = nuts.getfield local getnext = nuts.getnext local getprev = nuts.getprev local getboth = nuts.getboth local getid = nuts.getid local getsubtype = nuts.getsubtype local getchar = nuts.getchar local getfont = nuts.getfont local getfam = nuts.getfam local getattr = nuts.getattr local getlist = nuts.getlist local getwidth = nuts.getwidth local getheight = nuts.getheight local getdepth = nuts.getdepth local getnucleus = nuts.getnucleus local getsub = nuts.getsub local getsup = nuts.getsup local getsubpre = nuts.getsubpre local getsuppre = nuts.getsuppre local setnucleus = nuts.setnucleus local setsub = nuts.setsub local setsup = nuts.setsup local setsubpre = nuts.setsubpre local setsuppre = nuts.setsuppre local flushnode = nuts.flush local copy_node = nuts.copy local slide_nodes = nuts.slide local set_visual = nuts.setvisual local mlisttohlist = nuts.mlisttohlist local new_kern = nodepool.kern local new_submlist = nodepool.submlist local new_noad = nodepool.noad local new_delimiter = nodepool.delimiter local new_fence = nodepool.fence local fonthashes = fonts.hashes local fontdata = fonthashes.identifiers local fontcharacters = fonthashes.characters local fontitalics = fonthashes.italics local variables = interfaces.variables local texsetattribute = tex.setattribute local texgetattribute = tex.getattribute local getfontoffamily = tex.getfontoffamily local unsetvalue = attributes.unsetvalue local implement = interfaces.implement local v_reset = variables.reset local chardata = characters.data noads = noads or { } -- todo: only here local noads = noads noads.processors = noads.processors or { } local processors = noads.processors noads.handlers = noads.handlers or { } local handlers = noads.handlers local tasks = nodes.tasks local enableaction = tasks.enableaction local setaction = tasks.setaction local nodecodes = nodes.nodecodes local noadcodes = nodes.noadcodes local fencecodes = nodes.fencecodes local ordnoad_code = noadcodes.ord local opdisplaylimitsnoad_code = noadcodes.opdisplaylimits local oplimitsnoad_code = noadcodes.oplimits local opnolimitsnoad_code = noadcodes.opnolimits local binnoad_code = noadcodes.bin local relnoad_code = noadcodes.rel local opennoad_code = noadcodes.open local closenoad_code = noadcodes.close local punctnoad_code = noadcodes.punct local innernoad_code = noadcodes.inner local undernoad_code = noadcodes.under local overnoad_code = noadcodes.over local vcenternoad_code = noadcodes.vcenter local ordlimitsnoad_code = noadcodes.ordlimits or oplimitsnoad_code local noad_code = nodecodes.noad -- attr nucleus sub sup local accent_code = nodecodes.accent -- attr nucleus sub sup accent local radical_code = nodecodes.radical -- attr nucleus sub sup left degree local fraction_code = nodecodes.fraction -- attr nucleus sub sup left right local subbox_code = nodecodes.subbox -- attr list local submlist_code = nodecodes.submlist -- attr list local mathchar_code = nodecodes.mathchar -- attr fam char local mathtextchar_code = nodecodes.mathtextchar -- attr fam char local delimiter_code = nodecodes.delimiter -- attr small_fam small_char large_fam large_char ----- style_code = nodecodes.style -- attr style ----- parameter_code = nodecodes.parameter -- attr style local math_choice = nodecodes.choice -- attr display text script scriptscript local fence_code = nodecodes.fence -- attr subtype local leftfence_code = fencecodes.left local middlefence_code = fencecodes.middle local rightfence_code = fencecodes.right -- local mathclasses = mathematics.classes -- local fenceclasses = { -- [leftfence_code] = mathclasses.open, -- [middlefence_code] = mathclasses.middle, -- [rightfence_code] = mathclasses.close, -- } -- this initial stuff is tricky as we can have removed and new nodes with the same address -- the only way out is a free-per-page list of nodes (not bad anyway) -- local gf = getfield local gt = setmetatableindex("number") getfield = function(n,f) gt[f] = gt[f] + 1 return gf(n,f) end mathematics.GETFIELD = gt -- local sf = setfield local st = setmetatableindex("number") setfield = function(n,f,v) st[f] = st[f] + 1 sf(n,f,v) end mathematics.SETFIELD = st local function process(start,what,n,parent) if n then n = n + 1 else n = 0 end -- local initial = start -- slide_nodes(start) -- we still miss a prev in noads -- fences test code -- while start do local id = getid(start) if trace_processing then if id == noad_code then report_processing("%w%S, class %a",n*2,nutstring(start),noadcodes[getsubtype(start)]) elseif id == mathchar_code then local char = getchar(start) local font = getfont(start) local fam = getfam(start) report_processing("%w%S, family %a, font %a, char %a, shape %c",n*2,nutstring(start),fam,font,char,char) else report_processing("%w%S",n*2,nutstring(start)) end end local proc = what[id] if proc then -- report_processing("start processing") local done, newstart, newinitial = proc(start,what,n,parent) -- prev is bugged: or getprev(start) if newinitial then initial = newinitial -- temp hack .. we will make all return head if newstart then start = newstart -- report_processing("stop processing (new start)") else -- report_processing("quit processing (done)") break end else if newstart then start = newstart -- report_processing("stop processing (new start)") else -- report_processing("stop processing") end end elseif id == noad_code then -- single characters are like this local noad = getnucleus(start) if noad then process(noad,what,n,start) end -- list noad = getsup (start) if noad then process(noad,what,n,start) end -- list noad = getsub (start) if noad then process(noad,what,n,start) end -- list if getsubpre then noad = getsuppre (start) if noad then process(noad,what,n,start) end -- list noad = getsubpre (start) if noad then process(noad,what,n,start) end -- list end elseif id == mathchar_code or id == mathtextchar_code or id == delimiter_code then break elseif id == subbox_code or id == submlist_code then local noad = getlist(start) if noad then process(noad,what,n,start) end -- list (not getlist !) elseif id == fraction_code then local noad = getfield(start,"num") if noad then process(noad,what,n,start) end -- list noad = getfield(start,"denom") if noad then process(noad,what,n,start) end -- list noad = getfield(start,"left") if noad then process(noad,what,n,start) end -- delimiter noad = getfield(start,"right") if noad then process(noad,what,n,start) end -- delimiter elseif id == math_choice then local noad = getfield(start,"display") if noad then process(noad,what,n,start) end -- list noad = getfield(start,"text") if noad then process(noad,what,n,start) end -- list noad = getfield(start,"script") if noad then process(noad,what,n,start) end -- list noad = getfield(start,"scriptscript") if noad then process(noad,what,n,start) end -- list elseif id == fence_code then local noad = getfield(start,"delim") if noad then process(noad,what,n,start) end -- delimiter elseif id == radical_code then local noad = getnucleus(start) if noad then process(noad,what,n,start) end -- list noad = getsup (start) if noad then process(noad,what,n,start) end -- list noad = getsub (start) if noad then process(noad,what,n,start) end -- list if getsubpre then noad = getsuppre (start) if noad then process(noad,what,n,start) end -- list noad = getsubpre (start) if noad then process(noad,what,n,start) end -- list end noad = getfield(start,"left") if noad then process(noad,what,n,start) end -- delimiter noad = getfield(start,"degree") if noad then process(noad,what,n,start) end -- list elseif id == accent_code then local noad = getnucleus(start) if noad then process(noad,what,n,start) end -- list noad = getsup (start) if noad then process(noad,what,n,start) end -- list noad = getsub (start) if noad then process(noad,what,n,start) end -- list if getsubpre then noad = getsuppre (start) if noad then process(noad,what,n,start) end -- list noad = getsubpre (start) if noad then process(noad,what,n,start) end -- list end noad = getfield(start,"accent") if noad then process(noad,what,n,start) end -- list noad = getfield(start,"bot_accent") if noad then process(noad,what,n,start) end -- list -- elseif id == style_code then -- -- has a next -- elseif id == parameter_code then -- -- has a next -- else -- -- glue, penalty, etc end start = getnext(start) end if not parent then return initial -- only first level -- for now end end local function processnested(current,what,n) local noad = nil local id = getid(current) if id == noad_code then noad = getnucleus(current) if noad then process(noad,what,n,current) end -- list noad = getsup (current) if noad then process(noad,what,n,current) end -- list noad = getsub (current) if noad then process(noad,what,n,current) end -- list if getsubpre then noad = getsuppre (current) if noad then process(noad,what,n,current) end -- list noad = getsubpre (current) if noad then process(noad,what,n,current) end -- list end elseif id == subbox_code or id == submlist_code then noad = getlist(current) if noad then process(noad,what,n,current) end -- list (not getlist !) elseif id == fraction_code then noad = getfield(current,"num") if noad then process(noad,what,n,current) end -- list noad = getfield(current,"denom") if noad then process(noad,what,n,current) end -- list noad = getfield(current,"left") if noad then process(noad,what,n,current) end -- delimiter noad = getfield(current,"right") if noad then process(noad,what,n,current) end -- delimiter elseif id == math_choice then noad = getfield(current,"display") if noad then process(noad,what,n,current) end -- list noad = getfield(current,"text") if noad then process(noad,what,n,current) end -- list noad = getfield(current,"script") if noad then process(noad,what,n,current) end -- list noad = getfield(current,"scriptscript") if noad then process(noad,what,n,current) end -- list elseif id == fence_code then noad = getfield(current,"delim") if noad then process(noad,what,n,current) end -- delimiter elseif id == radical_code then noad = getnucleus(current) if noad then process(noad,what,n,current) end -- list noad = getsup (current) if noad then process(noad,what,n,current) end -- list noad = getsub (current) if noad then process(noad,what,n,current) end -- list if getsubpre then noad = getsuppre (current) if noad then process(noad,what,n,current) end -- list noad = getsubpre (current) if noad then process(noad,what,n,current) end -- list end noad = getfield(current,"left") if noad then process(noad,what,n,current) end -- delimiter noad = getfield(current,"degree") if noad then process(noad,what,n,current) end -- list elseif id == accent_code then noad = getnucleus(current) if noad then process(noad,what,n,current) end -- list noad = getsup (current) if noad then process(noad,what,n,current) end -- list noad = getsub (current) if noad then process(noad,what,n,current) end -- list if getsubpre then noad = getsuppre (current) if noad then process(noad,what,n,current) end -- list noad = getsubpre (current) if noad then process(noad,what,n,current) end -- list end noad = getfield(current,"accent") if noad then process(noad,what,n,current) end -- list noad = getfield(current,"bot_accent") if noad then process(noad,what,n,current) end -- list end end local function processstep(current,process,n,id) local noad = nil local id = id or getid(current) if id == noad_code then noad = getnucleus(current) if noad then process(noad,n,current) end -- list noad = getsup (current) if noad then process(noad,n,current) end -- list noad = getsub (current) if noad then process(noad,n,current) end -- list if getsubpre then noad = getsuppre (current) if noad then process(noad,n,current) end -- list noad = getsubpre (current) if noad then process(noad,n,current) end -- list end elseif id == subbox_code or id == submlist_code then noad = getlist(current) if noad then process(noad,n,current) end -- list (not getlist !) elseif id == fraction_code then noad = getfield(current,"num") if noad then process(noad,n,current) end -- list noad = getfield(current,"denom") if noad then process(noad,n,current) end -- list noad = getfield(current,"left") if noad then process(noad,n,current) end -- delimiter noad = getfield(current,"right") if noad then process(noad,n,current) end -- delimiter elseif id == math_choice then noad = getfield(current,"display") if noad then process(noad,n,current) end -- list noad = getfield(current,"text") if noad then process(noad,n,current) end -- list noad = getfield(current,"script") if noad then process(noad,n,current) end -- list noad = getfield(current,"scriptscript") if noad then process(noad,n,current) end -- list elseif id == fence_code then noad = getfield(current,"delim") if noad then process(noad,n,current) end -- delimiter elseif id == radical_code then noad = getnucleus(current) if noad then process(noad,n,current) end -- list noad = getsup (current) if noad then process(noad,n,current) end -- list noad = getsub (current) if noad then process(noad,n,current) end -- list if getsubpre then noad = getsuppre (current) if noad then process(noad,n,current) end -- list noad = getsubpre (current) if noad then process(noad,n,current) end -- list end noad = getfield(current,"left") if noad then process(noad,n,current) end -- delimiter noad = getfield(current,"degree") if noad then process(noad,n,current) end -- list elseif id == accent_code then noad = getnucleus(current) if noad then process(noad,n,current) end -- list noad = getsup (current) if noad then process(noad,n,current) end -- list noad = getsub (current) if noad then process(noad,n,current) end -- list if getsubpre then noad = getsuppre (current) if noad then process(noad,n,current) end -- list noad = getsubpre (current) if noad then process(noad,n,current) end -- list end noad = getfield(current,"accent") if noad then process(noad,n,current) end -- list noad = getfield(current,"bot_accent") if noad then process(noad,n,current) end -- list end end local function processnoads(head,actions,banner) if trace_processing then report_processing("start %a",banner) head = process(head,actions) report_processing("stop %a",banner) else head = process(head,actions) end return head end noads.process = processnoads noads.processnested = processnested noads.processouter = process -- experiment (when not present fall back to fam 0) -- needs documentation local unknowns = { } local checked = { } -- simple case local tracked = false trackers.register("fonts.missing", function(v) tracked = v end) local cached = setmetatableindex("table") -- complex case local function errorchar(font,char) local done = unknowns[char] if done then unknowns[char] = done + 1 else unknowns[char] = 1 end if tracked then -- slower as we check each font too and we always replace as math has -- more demands than text local fake = cached[font][char] if fake then return fake else local kind, fake = fonts.checkers.placeholder(font,char) if not fake or kind ~= "char" then -- Also check for "with" here? fake = 0x3F end cached[font][char] = fake return fake end else -- only simple checking, report at the end so one should take -- action anyway ... we can miss a few checks but that is ok -- as there is at least one reported if not checked[char] then if trace_normalizing then report_normalizing("character %C is not available",char) end checked[char] = true end return 0x3F end end -- 0-2 regular -- 3-5 bold -- 6-8 pseudobold -- this could best be integrated in the remapper, and if we run into problems, we -- might as well do this do local families = { } local a_mathfamily = privateattribute("mathfamily") local boldmap = mathematics.boldmap local familymap = { [0] = "regular", "regular", "regular", "bold", "bold", "bold", "pseudobold", "pseudobold", "pseudobold", } families[fraction_code] = function(pointer,what,n,parent) local a = getattr(pointer,a_mathfamily) if a and a >= 0 then if a > 0 then setattr(pointer,a_mathfamily,0) if a > 5 then a = a - 3 end end setfam(pointer,a) end processnested(pointer,families,n+1) end families[noad_code] = function(pointer,what,n,parent) local a = getattr(pointer,a_mathfamily) if a and a >= 0 then if a > 0 then setattr(pointer,a_mathfamily,0) if a > 5 then a = a - 3 end end setfam(pointer,a) end processnested(pointer,families,n+1) end families[mathchar_code] = function(pointer) if getfam(pointer) == 0 then local a = getattr(pointer,a_mathfamily) if a and a > 0 then setattr(pointer,a_mathfamily,0) if a > 5 then local char = getchar(pointer) local bold = boldmap[char] local newa = a - 3 if not bold then if trace_families then report_families("no bold replacement for %C, family %s with remap %s becomes %s with remap %s",char,a,familymap[a],newa,familymap[newa]) end setfam(pointer,newa) elseif not fontcharacters[getfontoffamily(newa)][bold] then if trace_families then report_families("no bold character for %C, family %s with remap %s becomes %s with remap %s",char,a,familymap[a],newa,familymap[newa]) end if newa > 3 then setfam(pointer,newa-3) end else setattr(pointer,a_exportstatus,char) setchar(pointer,bold) if trace_families then report_families("replacing %C by bold %C, family %s with remap %s becomes %s with remap %s",char,bold,a,familymap[a],newa,familymap[newa]) end setfam(pointer,newa) end else local char = getchar(pointer) if not fontcharacters[getfontoffamily(a)][char] then if trace_families then report_families("no bold replacement for %C",char) end else if trace_families then report_families("family of %C becomes %s with remap %s",char,a,familymap[a]) end setfam(pointer,a) end end end end end families[delimiter_code] = function(pointer) if getfield(pointer,"small_fam") == 0 then local a = getattr(pointer,a_mathfamily) if a and a > 0 then setattr(pointer,a_mathfamily,0) if a > 5 then -- no bold delimiters in unicode a = a - 3 end local char = getfield(pointer,"small_char") local okay = fontcharacters[getfontoffamily(a)][char] if okay then setfield(pointer,"small_fam",a) elseif a > 2 then setfield(pointer,"small_fam",a-3) end local char = getfield(pointer,"large_char") local okay = fontcharacters[getfontoffamily(a)][char] if okay then setfield(pointer,"large_fam",a) elseif a > 2 then setfield(pointer,"large_fam",a-3) end else setfield(pointer,"small_fam",0) setfield(pointer,"large_fam",0) end end end -- will become: -- families[delimiter_code] = function(pointer) -- if getfam(pointer) == 0 then -- local a = getattr(pointer,a_mathfamily) -- if a and a > 0 then -- setattr(pointer,a_mathfamily,0) -- if a > 5 then -- -- no bold delimiters in unicode -- a = a - 3 -- end -- local char = getchar(pointer) -- local okay = fontcharacters[getfontoffamily(a)][char] -- if okay then -- setfam(pointer,a) -- elseif a > 2 then -- setfam(pointer,a-3) -- end -- else -- setfam(pointer,0) -- end -- end -- end families[mathtextchar_code] = families[mathchar_code] function handlers.families(head,style,penalties) processnoads(head,families,"families") return true -- not needed end end -- character remapping do local a_mathalphabet = privateattribute("mathalphabet") local a_mathgreek = privateattribute("mathgreek") local relocate = { } local remapalphabets = mathematics.remapalphabets local fallbackstyleattr = mathematics.fallbackstyleattr local setnodecolor = colortracers.set local function report_remap(tag,id,old,new,extra) report_remapping("remapping %s in font (%s,%s) from %C to %C%s", tag,id,fontdata[id].properties.fontname or "",old,new,extra) end local function checked(pointer) local char = getchar(pointer) local font = getfont(pointer) local data = fontcharacters[font] if not data[char] then local specials = characters.data[char].specials if specials and (specials[1] == "char" or specials[1] == "font") then local newchar = specials[#specials] if trace_remapping then report_remap("fallback",font,char,newchar) end if trace_analyzing then setnodecolor(pointer,"font:isol") end setattr(pointer,a_exportstatus,char) -- testcase: exponentiale setchar(pointer,newchar) return true end end end relocate[mathchar_code] = function(pointer) local g = getattr(pointer,a_mathgreek) or 0 local a = getattr(pointer,a_mathalphabet) or 0 local char = getchar(pointer) local font = getfont(pointer) local characters = fontcharacters[font] if a > 0 or g > 0 then if a > 0 then setattr(pointer,a_mathgreek,0) end if g > 0 then setattr(pointer,a_mathalphabet,0) end local newchar = remapalphabets(char,a,g) if newchar then local newchardata = characters[newchar] if newchardata then if trace_remapping then report_remap("char",font,char,newchar,newchardata.commands and " (virtual)" or "") end if trace_analyzing then setnodecolor(pointer,"font:isol") end setchar(pointer,newchar) return true else local fallback = fallbackstyleattr(a) if fallback then local newchar = remapalphabets(char,fallback,g) if newchar then if characters[newchar] then if trace_remapping then report_remap("char",font,char,newchar," (fallback remapping used)") end if trace_analyzing then setnodecolor(pointer,"font:isol") end setchar(pointer,newchar) return true elseif trace_remapping then report_remap("char",font,char,newchar," fails (no fallback character)") end elseif trace_remapping then report_remap("char",font,char,newchar," fails (no fallback remap character)") end elseif trace_remapping then report_remap("char",font,char,newchar," fails (no fallback style)") end end elseif trace_remapping then local chardata = characters[char] if chardata and chardata.commands then report_remap("char",font,char,char," (virtual)") end end end if not characters[char] then setchar(pointer,errorchar(font,char)) end if trace_analyzing then setnodecolor(pointer,"font:medi") end if check_coverage then return checked(pointer) end end relocate[mathtextchar_code] = function(pointer) if trace_analyzing then setnodecolor(pointer,"font:init") end end relocate[delimiter_code] = function(pointer) if trace_analyzing then setnodecolor(pointer,"font:fina") end end function handlers.relocate(head,style,penalties) processnoads(head,relocate,"relocate") return true -- not needed end end -- rendering (beware, not exported) do local render = { } local rendersets = mathematics.renderings.numbers or { } -- store render[mathchar_code] = function(pointer) local attr = getattr(pointer,a_mathrendering) if attr and attr > 0 then local char = getchar(pointer) local renderset = rendersets[attr] if renderset then local newchar = renderset[char] if newchar then local font = getfont(pointer) local characters = fontcharacters[font] if characters and characters[newchar] then setchar(pointer,newchar) setattr(pointer,a_exportstatus,char) end end end end end function handlers.render(head,style,penalties) processnoads(head,render,"render") return true -- not needed end end -- some resize options (this works ok because the content is -- empty and no larger next will be forced) -- -- beware: we don't use \delcode but \Udelcode and as such have -- no large_fam; also, we need to check for subtype and/or -- small_fam not being 0 because \. sits in 0,0 by default -- -- todo: just replace the character by an ord noad -- and remove the right delimiter as well do local a_mathsize = privateattribute("mathsize") -- this might move into other fence code local resize = { } resize[fence_code] = function(pointer) local subtype = getsubtype(pointer) if subtype == leftfence_code or subtype == rightfence_code then local a = getattr(pointer,a_mathsize) if a and a > 0 then local method = div(a,100) local size = a % 100 setattr(pointer,a_mathsize,0) local delimiter = getfield(pointer,"delim") local chr = getchar(delimiter) if chr > 0 then local fam = getfam(delimiter) local id = getfontoffamily(fam) if id > 0 then local data = fontdata[id] local char = mathematics.big(data,chr,size,method) local ht = getheight(pointer) local dp = getdepth(pointer) if ht == 1 or dp == 1 then -- 1 scaled point is a signal local chardata = data.characters[char] if ht == 1 then setheight(pointer,chardata.height) end if dp == 1 then setdepth(pointer,chardata.depth) end end if trace_fences then report_fences("replacing %C by %C using method %a and size %a",chr,char,method,size) end setchar(delimiter,char) end end end end end function handlers.resize(head,style,penalties) processnoads(head,resize,"resize") return true -- not needed end end -- still not perfect: do local a_autofence = privateattribute("mathautofence") local autofences = { } local dummyfencechar = 0x2E local function makefence(what,char) local d = new_delimiter() -- todo: attr local f = new_fence() -- todo: attr if char then local sym = getnucleus(char) local chr = getchar(sym) local fam = getfam(sym) if chr == dummyfencechar then chr = 0 end setchar(d,chr) setfam(d,fam) flushnode(sym) end setattrlist(d,char) setattrlist(f,char) setsubtype(f,what) setfield(f,"delim",d) setfield(f,"class",-1) -- tex itself does this, so not fenceclasses[what] return f end local function show(where,pointer) print("") local i = 0 for n in nuts.traverse(pointer) do i = i + 1 print(i,where,nuts.tonode(n)) end print("") end local function makelist(middle,noad,f_o,o_next,c_prev,f_c) -- report_fences( -- "middle %s, noad %s, open %s, opennext %s, closeprev %s, close %s", -- middle or "?", -- noad or "?", -- f_o or "?", -- o_next or "?", -- c_prev or "?", -- f_c or "?" -- ) local list = new_submlist() setsubtype(noad,innernoad_code) setnucleus(noad,list) setlist(list,f_o) setlink(f_o,o_next) -- prev of list is nil setlink(c_prev,f_c) -- next of list is nil -- show("list",f_o) if middle and next(middle) then local prev = f_o local current = o_next while current ~= f_c do local midl = middle[current] local next = getnext(current) if midl then local fence = makefence(middlefence_code,current) setnucleus(current) flushnode(current) middle[current] = nil -- replace_node setlink(prev,fence,next) prev = fence else prev = current end current = next end end return noad end -- relinking is now somewhat overdone local function convert_both(open,close,middle) local o_next = getnext(open) if o_next == close then return close else local c_prev, c_next = getboth(close) local f_o = makefence(leftfence_code,open) local f_c = makefence(rightfence_code,close) makelist(middle,open,f_o,o_next,c_prev,f_c) setnucleus(close) flushnode(close) -- open is now a list setlink(open,c_next) return open end end local function convert_open(open,last,middle) -- last is really last (final case) local f_o = makefence(leftfence_code,open) local f_c = makefence(rightfence_code) local o_next = getnext(open) makelist(middle,open,f_o,o_next,last,nil) -- open is now a list setlink(open,l_next) return open end local function convert_close(first,close,middle) local f_o = makefence(leftfence_code) local f_c = makefence(rightfence_code,close) local c_prev = getprev(close) local f_next = getnext(first) makelist(middle, close, f_o,f_next,c_prev,f_c) -- close is now a list if c_prev ~= first then setlink(first,close) end return close end local stacks = setmetatableindex("table") -- 1=open 2=close 3=middle 4=both local function processfences(pointer,n,parent) local current = pointer local last = pointer local start = pointer local done = false local initial = pointer local stack = nil local middle = nil -- todo: use properties while current do -- show("before",pointer) local id = getid(current) if id == noad_code then local a = getattr(current,a_autofence) if a and a > 0 then local stack = stacks[n] setattr(current,a_autofence,0) -- hm, better use a property local level = #stack if a == 1 then if trace_fences then report_fences("%2i: level %i, handling %s, action %s",n,level,"open","open") end insert(stack,current) elseif a == 2 then local open = remove(stack) if open then if trace_fences then report_fences("%2i: level %i, handling %s, action %s",n,level,"close","both") end current = convert_both(open,current,middle) elseif current == start then if trace_fences then report_fences("%2i: level %i, handling %s, action %s",n,level,"close","skip") end else if trace_fences then report_fences("%2i: level %i, handling %s, action %s",n,level,"close","close") end current = convert_close(initial,current,middle) if not parent then initial = current end end elseif a == 3 then if trace_fences then report_fences("%2i: level %i, handling %s, action %s",n,level,"middle","middle") end if middle then middle[current] = last else middle = { [current] = last } end elseif a == 4 then if not stack or #stack == 0 then if trace_fences then report_fences("%2i: level %i, handling %s, action %s",n,level,"both","open") end insert(stack,current) else local open = remove(stack) if open then if trace_fences then report_fences("%2i: level %i, handling %s, action %s",n,level,"both","both") end current = convert_both(open,current,middle) elseif current == start then if trace_fences then report_fences("%2i: level %i, handling %s, action %s",n,level,"both","skip") end else if trace_fences then report_fences("%2i: level %i, handling %s, action %s",n,level,"both","close") end current = convert_close(initial,current,middle) if not parent then initial = current end end end end done = true else processstep(current,processfences,n+1,id) end else -- next at current level processstep(current,processfences,n,id) end -- show("after",pointer) last = current current = getnext(current) end if done then local stack = stacks[n] local s = #stack if s > 0 then for i=1,s do local open = remove(stack) if trace_fences then report_fences("%2i: level %i, handling %s, action %s",n,#stack,"flush","open") end last = convert_open(open,last,middle) end -- show("done",pointer) end end end -- we can have a first changed node .. an option is to have a leading dummy node in math -- lists like the par node as it can save a lot of mess local enabled = false implement { name = "enableautofences", onlyonce = true, actions = function() enableaction("math","noads.handlers.autofences") enabled = true end } function handlers.autofences(head,style,penalties) if enabled then -- tex.modes.c_math_fences_auto -- inspect(nodes.totree(head)) processfences(head,1) -- inspect(nodes.totree(head)) end end end -- normalize scripts do local unscript = { } noads.processors.unscript = unscript local superscripts = characters.superscripts local subscripts = characters.subscripts local fractions = characters.fractions local replaced = { } local function replace(pointer,what,n,parent) pointer = parent -- we're following the parent list (chars trigger this) local next = getnext(pointer) local start_super, stop_super, start_sub, stop_sub local mode = "unset" while next and getid(next) == noad_code do local nextnucleus = getnucleus(next) if nextnucleus and getid(nextnucleus) == mathchar_code and not getsub(next) and not getsup(next) then local char = getchar(nextnucleus) local s = superscripts[char] if s then if not start_super then start_super = next mode = "super" elseif mode == "sub" then break end stop_super = next next = getnext(next) setchar(nextnucleus,s) replaced[char] = (replaced[char] or 0) + 1 if trace_normalizing then report_normalizing("superscript %C becomes %C",char,s) end else local s = subscripts[char] if s then if not start_sub then start_sub = next mode = "sub" elseif mode == "super" then break end stop_sub = next next = getnext(next) setchar(nextnucleus,s) replaced[char] = (replaced[char] or 0) + 1 if trace_normalizing then report_normalizing("subscript %C becomes %C",char,s) end else break end end else break end end if start_super then if start_super == stop_super then setsup(pointer,getnucleus(start_super)) else local list = new_submlist() -- todo attr setlist(list,start_super) setsup(pointer,list) end if mode == "super" then setnext(pointer,getnext(stop_super)) end setnext(stop_super) end if start_sub then -- if mode == "sub" then -- local sup = getsup(pointer) -- if sup and not getsub(pointer) then -- local nxt = getnext(pointer) -- local new = new_noad(pointer) -- setnucleus(new,new_submlist()) -- setlink(pointer,new,nxt) -- pointer = new -- end -- end if start_sub == stop_sub then setsub(pointer,getnucleus(start_sub)) else local list = new_submlist() -- todo attr setlist(list,start_sub) setsub(pointer,list) end if mode == "sub" then setnext(pointer,getnext(stop_sub)) end setnext(stop_sub) end -- we could return stop end unscript[mathchar_code] = replace -- not noads as we need to recurse function handlers.unscript(head,style,penalties) processnoads(head,unscript,"unscript") return true -- not needed end end do local unstack = { } noads.processors.unstack = unstack local enabled = false local a_unstack = privateattribute("mathunstack") unstack[noad_code] = function(pointer) if getattr(pointer,a_unstack) then local sup = getsup(pointer) local sub = getsub(pointer) if sup and sub then -- if trace_unstacking then -- report_unstacking() -- todo ... what to show ... -- end local nxt = getnext(pointer) local new = new_noad(pointer) setnucleus(new,new_submlist()) setsub(pointer) setsub(new,sub) setlink(pointer,new,nxt) end end end function handlers.unstack(head,style,penalties) if enabled then processnoads(head,unstack,"unstack") return true -- not needed end end implement { name = "enablescriptunstacking", onlyonce = true, actions = function() enableaction("math","noads.handlers.unstack") enabled = true end } end do local function collected(list) if list and next(list) then local n, t = 0, { } for k, v in sortedhash(list) do n = n + 1 t[n] = formatters["%C"](k) end return formatters["% t (n=%s)"](t,n) end end statistics.register("math script replacements", function() return collected(replaced) end) statistics.register("unknown math characters", function() return collected(unknowns) end) end -- math alternates: (in xits lgf: $ABC$ $\cal ABC$ $\mathalternate{cal}\cal ABC$) -- math alternates: (in lucidaot lgf: $ABC \mathalternate{italic} ABC$) -- todo: set alternate for specific symbols -- todo: no need to do this when already loaded -- todo: use a fonts.hashes.mathalternates do local last = 0 local known = setmetatableindex(function(t,k) local v = bor(0,2^last) t[k] = v last = last + 1 return v end) local defaults = { dotless = { feature = 'dtls', value = 1, comment = "Mathematical Dotless Forms" }, -- zero = { feature = 'zero', value = 1, comment = "Slashed or Dotted Zero" }, -- in no math font (yet) } local function initializemathalternates(tfmdata) local goodies = tfmdata.goodies local autolist = defaults -- table.copy(defaults) local function setthem(newalternates) local resources = tfmdata.resources -- was tfmdata.shared local mathalternates = resources.mathalternates local alternates, attributes, registered, presets if mathalternates then alternates = mathalternates.alternates attributes = mathalternates.attributes registered = mathalternates.registered else alternates, attributes, registered = { }, { }, { } mathalternates = { attributes = attributes, alternates = alternates, registered = registered, presets = { }, resets = { }, hashes = setmetatableindex("table") } resources.mathalternates = mathalternates end -- for name, data in sortedhash(newalternates) do if alternates[name] then -- ignore else local attr = known[name] attributes[attr] = data alternates[name] = attr registered[#registered+1] = attr end end end if goodies then local done = { } for i=1,#goodies do -- first one counts -- we can consider sharing the attributes ... todo (only once scan) local mathgoodies = goodies[i].mathematics local alternates = mathgoodies and mathgoodies.alternates if alternates then if trace_goodies then report_goodies("loading alternates for font %a",tfmdata.properties.name) end for k, v in next, autolist do if not alternates[k] then alternates[k] = v end end setthem(alternates) return end end end if trace_goodies then report_goodies("loading default alternates for font %a",tfmdata.properties.name) end setthem(autolist) end registerotffeature { name = "mathalternates", description = "additional math alternative shapes", initializers = { base = initializemathalternates, node = initializemathalternates, } } -- local getalternate = otf.getalternate (runtime new method so ...) -- todo: not shared but copies ... one never knows local a_mathalternate = privateattribute("mathalternate") local alternate = { } -- processors.alternate = alternate local fontdata = fonts.hashes.identifiers local fontresources = fonts.hashes.resources local function getalternate(fam,tag,current) local resources = fontresources[getfontoffamily(fam)] local attribute = unsetvalue if resources then local mathalternates = resources.mathalternates if mathalternates then local presets = mathalternates.presets if presets then local resets = mathalternates.resets attribute = presets[tag] if not attribute then attribute = 0 local alternates = mathalternates.alternates for s in gmatch(tag,"[^, ]+") do if s == v_reset then resets[tag] = true current = unsetvalue else local a = alternates[s] -- or known[s] if a then attribute = bor(attribute,a) end end end if attribute == 0 then attribute = unsetvalue end presets[tag] = attribute elseif resets[tag] then current = unsetvalue end end end end if attribute > 0 and current and current > 0 then return bor(current,attribute) else return attribute end end local function presetalternate(fam,tag) texsetattribute(a_mathalternate,getalternate(fam,tag)) end implement { name = "presetmathalternate", actions = presetalternate, arguments = { "integer", "string" } } local function setalternate(fam,tag) local a = texgetattribute(a_mathalternate) local v = getalternate(fam,tag,a) texsetattribute(a_mathalternate,v) end implement { name = "setmathalternate", actions = setalternate, arguments = { "integer", "string" } } alternate[mathchar_code] = function(pointer) -- slow local a = getattr(pointer,a_mathalternate) if a and a > 0 then setattr(pointer,a_mathalternate,0) local fontid = getfont(pointer) local resources = fontresources[fontid] if resources then local mathalternates = resources.mathalternates if mathalternates then local attributes = mathalternates.attributes local registered = mathalternates.registered local hashes = mathalternates.hashes for i=1,#registered do local r = registered[i] if band(a,r) ~= 0 then local char = getchar(pointer) local alt = hashes[i][char] if alt == nil then local what = attributes[r] alt = otf.getalternate(fontdata[fontid],char,what.feature,what.value) or false if alt == char then alt = false end hashes[i][char] = alt end if alt then if trace_alternates then local what = attributes[r] report_alternates("alternate %a, value %a, replacing glyph %U by glyph %U", tostring(what.feature),tostring(what.value),getchar(pointer),alt) end setchar(pointer,alt) break end end end end end end end function handlers.alternates(head,style,penalties) processnoads(head,alternate,"alternate") return true -- not needed end end -- italics: we assume that only characters matter -- -- = we check for correction first because accessing nodes is slower -- = the actual glyph is not that important (we can control it with numbers) -- Italic correction in luatex math is (was) a mess. There are all kind of assumptions based on -- old fonts and new fonts. Eventually there should be a flag that can signal to ignore all -- those heuristics. We want to deal with it ourselves also in the perspective of mixed math -- and text. Also, for a while in context we had to deal with a mix of virtual math fonts and -- real ones. -- in opentype the italic correction of a limop is added to the width and luatex does -- some juggling that we want to avoid but we need to do something here (in fact, we could -- better fix the width of the character) do local a_mathitalics = privateattribute("mathitalics") local italics = { } local default_factor = 1/20 local setcolor = colortracers.set local resetcolor = colortracers.reset local italic_kern = new_kern local c_positive_d = "trace:dg" local c_negative_d = "trace:dr" local function insert_kern(current,kern) local sub = new_submlist() -- todo: attr local noad = new_noad() -- todo: attr setlist(sub,kern) setnext(kern,noad) setnucleus(noad,current) return sub end registertracker("math.italics.visualize", function(v) if v then italic_kern = function(k) local n = new_kern(k) -- todo: attr set_visual(n,"italic") return n end else italic_kern = new_kern end end) local function getcorrection(method,font,char) -- -- or character.italic -- (this one is for tex) local visual = chardata[char].visual if method == 1 then -- check on state local italics = fontitalics[font] if italics then local character = fontcharacters[font][char] if character then local correction = character.italic if correction and correction ~= 0 then return correction, visual end end end elseif method == 2 then -- no check local character = fontcharacters[font][char] if character then local correction = character.italic if correction and correction ~= 0 then return correction, visual end end elseif method == 3 then -- check on visual if visual == "it" or visual == "bi" then local character = fontcharacters[font][char] if character then local correction = character.italic if correction and correction ~= 0 then return correction, visual end end end elseif method == 4 then -- combination of 1 and 3 local italics = fontitalics[font] if italics and (visual == "it" or visual == "bi") then local character = fontcharacters[font][char] if character then local correction = character.italic if correction and correction ~= 0 then return correction, visual end end end end end italics[mathchar_code] = function(pointer,what,n,parent) local method = getattr(pointer,a_mathitalics) if method and method > 0 and method < 100 then local char = getchar(pointer) local font = getfont(pointer) local correction, visual = getcorrection(method,font,char) if correction and correction ~= 0 then local next_noad = getnext(parent) if not next_noad then if n == 1 then -- only at the outer level .. will become an option (always,endonly,none) if trace_italics then report_italics("method %a, flagging italic correction %p between %C and end math",method,correction,char) end if correction > 0 then correction = correction + 100 else correction = correction - 100 end correction = round(correction) setattr(pointer,a_mathitalics,correction) setattr(parent,a_mathitalics,correction) return -- so no reset later on end end end end setattr(pointer,a_mathitalics,unsetvalue) end function handlers.italics(head,style,penalties) processnoads(head,italics,"italics") return true -- not needed end local enable = function() enableaction("math", "noads.handlers.italics") if trace_italics then report_italics("enabling math italics") end -- we enable math (unless already enabled elsewhere) typesetters.italics.enablemath() enable = false end -- best do this only on math mode (less overhead) function mathematics.setitalics(name) if enable then enable() end texsetattribute(a_mathitalics,name and name ~= v_reset and tonumber(name) or unsetvalue) -- maybe also v_none end function mathematics.getitalics(name) if enable then enable() end context(name and name ~= v_reset and tonumber(name) or unsetvalue) end function mathematics.resetitalics() texsetattribute(a_mathitalics,unsetvalue) end implement { name = "initializemathitalics", actions = enable, onlyonce = true, } implement { name = "setmathitalics", actions = mathematics.setitalics, arguments = "string", } implement { name = "getmathitalics", actions = mathematics.getitalics, arguments = "string", } implement { name = "resetmathitalics", actions = mathematics.resetitalics } end do -- math kerns (experiment) in goodies: -- -- mathematics = { -- kernpairs = { -- [0x1D44E] = { -- [0x1D44F] = 400, -- 𝑎𝑏 -- } -- }, -- } local a_kernpairs = privateattribute("mathkernpairs") local kernpairs = { } local function enable() enableaction("math", "noads.handlers.kernpairs") if trace_kernpairs then report_kernpairs("enabling math kern pairs") end enable = false end implement { name = "initializemathkernpairs", actions = enable, onlyonce = true, } local hash = setmetatableindex(function(t,font) local g = fontdata[font].goodies local m = g and g[1] and g[1].mathematics local k = m and m.kernpairs t[font] = k return k end) -- no correction after prime because that moved to a superscript kernpairs[mathchar_code] = function(pointer,what,n,parent) if getattr(pointer,a_kernpairs) == 1 then local font = getfont(pointer) local list = hash[font] if list then local first = getchar(pointer) local found = list[first] if found then local next = getnext(parent) if next and getid(next) == noad_code then pointer = getnucleus(next) if pointer then if getfont(pointer) == font then local second = getchar(pointer) local kern = found[second] if kern then kern = kern * fonts.hashes.parameters[font].hfactor if trace_kernpairs then report_kernpairs("adding %p kerning between %C and %C",kern,first,second) end setlink(parent,new_kern(kern),getnext(parent)) -- todo: attr end end end end end end end end function handlers.kernpairs(head,style,penalties) processnoads(head,kernpairs,"kernpairs") return true -- not needed end end -- primes and such do -- is validpair stil needed? local a_mathcollapsing = privateattribute("mathcollapsing") local collapse = { } local mathlists = characters.mathlists local validpair = { [ordnoad_code] = true, [opdisplaylimitsnoad_code] = true, [oplimitsnoad_code] = true, [opnolimitsnoad_code] = true, [binnoad_code] = true, -- new [relnoad_code] = true, [opennoad_code] = true, -- new [closenoad_code] = true, -- new [punctnoad_code] = true, -- new [innernoad_code] = false, [undernoad_code] = false, [overnoad_code] = false, [vcenternoad_code] = false, [ordlimitsnoad_code] = true, } local reported = setmetatableindex("table") collapse[mathchar_code] = function(pointer,what,n,parent) if parent and mathlists[getchar(pointer)] then local found, last, lucleus, lsup, lsub, category local tree = mathlists local current = parent while current and validpair[getsubtype(current)] do local nucleus = getnucleus(current) -- == pointer local sub = getsub(current) local sup = getsup(current) local char = getchar(nucleus) if char then local match = tree[char] if match then local method = getattr(current,a_mathcollapsing) if method and method > 0 and method <= 3 then local specials = match.specials local mathlist = match.mathlist local ligature if method == 1 then ligature = specials elseif method == 2 then ligature = specials or mathlist else -- 3 ligature = mathlist or specials end if ligature then category = mathlist and "mathlist" or "specials" found = ligature last = current lucleus = nucleus lsup = sup lsub = sub end tree = match if sub or sup then break else current = getnext(current) end else break end else break end else break end end if found and last and lucleus then local id = getfont(lucleus) local characters = fontcharacters[id] local replace = characters and characters[found] if not replace then if not reported[id][found] then reported[id][found] = true report_collapsing("%s ligature %C from %s","ignoring",found,category) end elseif trace_collapsing then report_collapsing("%s ligature %C from %s","creating",found,category) end setchar(pointer,found) local l = getnext(last) local c = getnext(parent) if lsub then setsub(parent,lsub) setsub(last) end if lsup then setsup(parent,lsup) setsup(last) end while c ~= l do local n = getnext(c) flushnode(c) c = n end setlink(parent,l) end end end function noads.handlers.collapse(head,style,penalties) processnoads(head,collapse,"collapse") return true -- not needed end local enable = function() enableaction("math", "noads.handlers.collapse") if trace_collapsing then report_collapsing("enabling math collapsing") end enable = false end implement { name = "initializemathcollapsing", actions = enable, onlyonce = true, } end do -- inner under over vcenter local fixscripts = { } local movesub = { -- primes [0x2032] = 0xFE932, [0x2033] = 0xFE933, [0x2034] = 0xFE934, [0x2057] = 0xFE957, -- reverse primes [0x2035] = 0xFE935, [0x2036] = 0xFE936, [0x2037] = 0xFE937, } mathematics.virtualize(movesub) local function fixsupscript(parent,current,current_char,new_char) if new_char ~= current_char and new_char ~= true then setchar(current,new_char) if trace_fixing then report_fixing("fixing subscript, replacing superscript %U by %U",current_char,new_char) end else if trace_fixing then report_fixing("fixing subscript, superscript %U",current_char) end end setfield(parent,"options",0x08+0x22) end -- local function movesubscript(parent,current_nucleus,oldchar,newchar) -- local prev = getprev(parent) -- if prev and getid(prev) == noad_code then -- local psup = getsup(prev) -- local psub = getsub(prev) -- if not psup and not psub then -- fixsupscript(prev,current_nucleus,oldchar,newchar) -- local nucleus = getnucleus(parent) -- local sub = getsub(parent) -- setsup(prev,nucleus) -- setsub(prev,sub) -- local dummy = copy_node(nucleus) -- setchar(dummy,0) -- setnucleus(parent,dummy) -- setsub(parent) -- elseif not psup then -- fixsupscript(prev,current_nucleus,oldchar,newchar) -- local nucleus = getnucleus(parent) -- setsup(prev,nucleus) -- local dummy = copy_node(nucleus) -- setchar(dummy,0) -- setnucleus(parent,dummy) -- end -- end -- end local function move_none_none(parent,prev,nuc,oldchar,newchar) fixsupscript(prev,nuc,oldchar,newchar) local sub = getsub(parent) setsup(prev,nuc) setsub(prev,sub) local dummy = copy_node(nuc) setchar(dummy,0) setnucleus(parent,dummy) setsub(parent) end local function move_none_psub(parent,prev,nuc,oldchar,newchar) fixsupscript(prev,nuc,oldchar,newchar) setsup(prev,nuc) local dummy = copy_node(nuc) setchar(dummy,0) setnucleus(parent,dummy) end fixscripts[mathchar_code] = function(pointer,what,n,parent,nested) -- todo: switch to turn in on and off if parent then local oldchar = getchar(pointer) local newchar = movesub[oldchar] if newchar then local nuc = getnucleus(parent) if pointer == nuc then local sub = getsub(pointer) local sup = getsup(pointer) if sub then if sup then -- print("[char] sub sup") else -- print("[char] sub ---") end elseif sup then -- print("[char] --- sup") else local prev = getprev(parent) if prev and getid(prev) == noad_code then local psub = getsub(prev) local psup = getsup(prev) if psub then if psup then -- print("sub sup [char] --- ---") else -- print("sub --- [char] --- ---") move_none_psub(parent,prev,nuc,oldchar,newchar) end elseif psup then -- print("--- sup [char] --- ---") else -- print("[char] --- ---") move_none_none(parent,prev,nuc,oldchar,newchar) end else -- print("no prev [char]") end end else -- print("[char]") end end end end function noads.handlers.fixscripts(head,style,penalties) processnoads(head,fixscripts,"fixscripts") return true -- not needed end end -- variants do local variants = { } local validvariants = { -- fast check on valid [0x2229] = 0xFE00, [0x222A] = 0xFE00, [0x2268] = 0xFE00, [0x2269] = 0xFE00, [0x2272] = 0xFE00, [0x2273] = 0xFE00, [0x228A] = 0xFE00, [0x228B] = 0xFE00, [0x2293] = 0xFE00, [0x2294] = 0xFE00, [0x2295] = 0xFE00, [0x2297] = 0xFE00, [0x229C] = 0xFE00, [0x22DA] = 0xFE00, [0x22DB] = 0xFE00, [0x2A3C] = 0xFE00, [0x2A3D] = 0xFE00, [0x2A9D] = 0xFE00, [0x2A9E] = 0xFE00, [0x2AAC] = 0xFE00, [0x2AAD] = 0xFE00, [0x2ACB] = 0xFE00, [0x2ACC] = 0xFE00, } variants[mathchar_code] = function(pointer,what,n,parent) -- also set export value local char = getchar(pointer) local selector = validvariants[char] if selector then local next = getnext(parent) if next and getid(next) == noad_code then local nucleus = getnucleus(next) if nucleus and getid(nucleus) == mathchar_code and getchar(nucleus) == selector then local variant local tfmdata = fontdata[getfont(pointer)] local mathvariants = tfmdata.resources.variants -- and variantdata if mathvariants then mathvariants = mathvariants[selector] if mathvariants then variant = mathvariants[char] end end if variant then setchar(pointer,variant) setattr(pointer,a_exportstatus,char) -- we don't export the variant as it's visual markup if trace_variants then report_variants("variant (%U,%U) replaced by %U",char,selector,variant) end else if trace_variants then report_variants("no variant (%U,%U)",char,selector) end end setprev(next,pointer) setnext(parent,getnext(next)) flushnode(next) end end end end function handlers.variants(head,style,penalties) processnoads(head,variants,"unicode variant") return true -- not needed end end -- for manuals do local classes = { } local colors = { [relnoad_code] = "trace:dr", [ordnoad_code] = "trace:db", [binnoad_code] = "trace:dg", [opennoad_code] = "trace:dm", [closenoad_code] = "trace:dm", [punctnoad_code] = "trace:dc", -- [opdisplaylimitsnoad_code] = "", -- [oplimitsnoad_code] = "", -- [opnolimitsnoad_code] = "", -- [ordlimitsnoad_code] = "", -- [innernoad_code = "", -- [undernoad_code] = "", -- [overnoad_code] = "", -- [vcenternoad_code] = "", } local setcolor = colortracers.set local resetcolor = colortracers.reset classes[mathchar_code] = function(pointer,what,n,parent) local color = colors[getsubtype(parent)] if color then setcolor(pointer,color) else resetcolor(pointer) end end function handlers.classes(head,style,penalties) processnoads(head,classes,"classes") return true -- not needed end registertracker("math.classes",function(v) setaction("math","noads.handlers.classes",v) end) end -- experimental do -- mathematics.registerdomain { -- name = "foo", -- parents = { "bar" }, -- characters = { -- [0x123] = { char = 0x234, class = binary }, -- }, -- } local domains = { } local categories = { } local numbers = { } local a_mathdomain = privateattribute("mathdomain") mathematics.domains = categories local permitted = { ordinary = ordnoad_code, binary = binnoad_code, relation = relnoad_code, punctuation = punctnoad_code, inner = innernoad_code, } function mathematics.registerdomain(data) local name = data.name if not name then return end local attr = #numbers + 1 categories[name] = data numbers[attr] = data data.attribute = attr -- we delay hashing return attr end local enable enable = function() enableaction("math", "noads.handlers.domains") if trace_domains then report_domains("enabling math domains") end enable = false end function mathematics.setdomain(name) if enable then enable() end local data = name and name ~= v_reset and categories[name] texsetattribute(a_mathdomain,data and data.attribute or unsetvalue) end function mathematics.getdomain(name) if enable then enable() end local data = name and name ~= v_reset and categories[name] context(data and data.attribute or unsetvalue) end implement { name = "initializemathdomain", actions = enable, onlyonce = true, } implement { name = "setmathdomain", arguments = "string", actions = mathematics.setdomain, } implement { name = "getmathdomain", arguments = "string", actions = mathematics.getdomain, } local function makehash(data) local hash = { } local parents = data.parents if parents then local function merge(name) if name then local c = categories[name] if c then local hash = c.hash if not hash then hash = makehash(c) end for k, v in next, hash do hash[k] = v end end end end if type(parents) == "string" then merge(parents) elseif type(parents) == "table" then for i=1,#parents do merge(parents[i]) end end end local characters = data.characters if characters then for k, v in next, characters do -- local chr = n.char local cls = v.class if cls then v.code = permitted[cls] else -- invalid class end hash[k] = v end end data.hash = hash return hash end domains[mathchar_code] = function(pointer,what,n,parent) local attr = getattr(pointer,a_mathdomain) if attr then local domain = numbers[attr] if domain then local hash = domain.hash if not hash then hash = makehash(domain) end local char = getchar(pointer) local okay = hash[char] if okay then local chr = okay.char local cls = okay.code if chr and chr ~= char then setchar(pointer,chr) end if cls and cls ~= getsubtype(parent) then setsubtype(parent,cls) end end end end end function handlers.domains(head,style,penalties) processnoads(head,domains,"domains") return true -- not needed end end -- just for me function handlers.showtree(head,style,penalties) inspect(nodes.totree(tonut(head))) end registertracker("math.showtree",function(v) setaction("math","noads.handlers.showtree",v) end) -- also for me do local applyvisuals = nuts.applyvisuals local visual = false function handlers.makeup(head) applyvisuals(head,visual) end registertracker("math.makeup",function(v) visual = v setaction("math","noads.handlers.makeup",v) end) end -- the normal builder do local force_penalties = false -- registertracker("math.penalties",function(v) -- force_penalties = v -- end) function builders.kernel.mlisttohlist(head,style,penalties) return mlisttohlist(head,style,force_penalties or penalties) end implement { name = "setmathpenalties", arguments = "integer", actions = function(p) force_penalties = p > 0 end, } end local actions = tasks.actions("math") -- head, style, penalties local starttiming, stoptiming = statistics.starttiming, statistics.stoptiming function processors.mlisttohlist(head,style,penalties) starttiming(noads) head = actions(head,style,penalties) stoptiming(noads) return head end callbacks.register('mlist_to_hlist',processors.mlisttohlist,"preprocessing math list") -- tracing statistics.register("math processing time", function() return statistics.elapsedseconds(noads) end)