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" } -- TODO: SET CLASSES ! -- if specials and (specials[1] == "char" or specials[1] == "font") then -- can we avoid this -- ... better create a reverse mapping from the already present vectors -- 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 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 texgetmode = tex.getmode local mathmode_code = tex.modelevels.math 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 trace_snapping = false registertracker("math.snapping", function(v) trace_snapping = v end) local check_coverage = true registerdirective("math.checkcoverage", function(v) check_coverage = v end) local use_math_goodies = true registerdirective("math.nogoodies", function(v) use_math_goodies = not 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 report_snapping = logreporter("mathematics","snapping") 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 getcharspec = nuts.getcharspec local getattr = nuts.getattr local getattrs = nuts.getattrs local getlist = nuts.getlist local getwidth = nuts.getwidth local getheight = nuts.getheight local getdepth = nuts.getdepth local getwhd = nuts.getwhd 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 getoffsets = nuts.getoffsets local setoffsets = nuts.setoffsets 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 fontparameters = fonthashes.parameters 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 v_small = variables.small local v_medium = variables.medium local v_big = variables.big local v_line = variables.line 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 ordinarynoad_code = noadcodes.ordinary local operatornoad_code = noadcodes.operator local binarynoad_code = noadcodes.binary local relationnoad_code = noadcodes.relation local opennoad_code = noadcodes.open local closenoad_code = noadcodes.close local middlenoad_code = noadcodes.middle local punctuationnoad_code = noadcodes.punctuation local innernoad_code = noadcodes.inner local fencednoad_code = noadcodes.fenced local undernoad_code = noadcodes.under local overnoad_code = noadcodes.over local vcenternoad_code = noadcodes.vcenter local fractionnoad_code = noadcodes.fraction local radicalnoad_code = noadcodes.radical local accentnoad_code = noadcodes.accent 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 ----- style_code = nodecodes.style ----- parameter_code = nodecodes.parameter local math_choice = nodecodes.choice local fence_code = nodecodes.fence local leftfence_code = fencecodes.left local middlefence_code = fencecodes.middle local rightfence_code = fencecodes.right -- 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 -- TODO : get rid of done 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, font, fam = getcharspec(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 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 noad = getfield(start,"prime") if noad then process(noad,what,n,start) end -- list 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,"delimiter") if noad then process(noad,what,n,start) end -- delimiter noad = getfield(start,"top") if noad then process(noad,what,n,start) end -- list noad = getfield(start,"bottom") if noad then process(noad,what,n,start) end -- list 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 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 noad = getfield(start,"prime") if noad then process(noad,what,n,start) end -- delimiter 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 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 noad = getfield(start,"prime") if noad then process(noad,what,n,start) end -- list noad = getfield(start,"topaccent") if noad then process(noad,what,n,start) end -- list noad = getfield(start,"botaccent") 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 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 noad = getfield (current,"prime") if noad then process(noad,what,n,current) end -- list 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,"delimiter") if noad then process(noad,what,n,current) end -- delimiter noad = getfield(current,"top") if noad then process(noad,what,n,current) end -- list noad = getfield(current,"bottom") if noad then process(noad,what,n,current) end -- list 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 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 noad = getfield(current,"prime") 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,"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 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 noad = getfield(current,"prime") if noad then process(noad,what,n,current) end -- list noad = getfield(current,"topaccent") if noad then process(noad,what,n,current) end -- list noad = getfield(current,"botaccent") 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 noad = getsuppre (current) if noad then process(noad,n,current) end -- list noad = getsubpre (current) if noad then process(noad,n,current) end -- list noad = getfield (current,"prime") if noad then process(noad,n,current) end -- list 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,"delimiter") if noad then process(noad,n,current) end -- delimiter noad = getfield(current,"top") if noad then process(noad,n,current) end -- list noad = getfield(current,"bottom") if noad then process(noad,n,current) end -- list 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 noad = getsuppre (current) if noad then process(noad,n,current) end -- list noad = getsubpre (current) if noad then process(noad,n,current) end -- list noad = getfield(current,"prime") if noad then process(noad,n,current) end -- delimiter 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 noad = getsuppre (current) if noad then process(noad,n,current) end -- list noad = getsubpre (current) if noad then process(noad,n,current) end -- list noad = getfield(current,"prime") if noad then process(noad,n,current) end -- list noad = getfield(current,"topaccent") if noad then process(noad,n,current) end -- list noad = getfield(current,"botaccent") 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 variantselectors = { [0xFE00] = true, [0xFE01] = true } local function errorchar(font,char) if variantselectors[char] then return char end 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,"smallfamily") == 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 fam = getfontoffamily(a) local char = getfield(pointer,"smallchar") local okay = fontcharacters[fam][char] if okay then setfield(pointer,"smallfamily",a) elseif a > 2 then setfield(pointer,"smallfamily",a-3) end local char = getfield(pointer,"largechar") local okay = fontcharacters[fam][char] if okay then setfield(pointer,"largefamily",a) elseif a > 2 then setfield(pointer,"largefamily",a-3) end else setfield(pointer,"smallfamily",0) setfield(pointer,"largefamily",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) if new then report_remapping("remapping %s in font (%s,%s) from %C to %C%s", tag,id,fontdata[id].properties.fontname or "",old,new,extra) else -- error ! report_remapping("remapping %s in font (%s,%s) from %C to ?", tag,id,fontdata[id].properties.fontname or "",old) end end local function checked(pointer) local char, font = getcharspec(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 -- We can optimize this if we really think that math is a bottleneck which it never -- really is. Beware: the font is the text font in the family, so we only check the -- text font here. relocate[mathchar_code] = function(pointer) local g = getattr(pointer,a_mathgreek) or 0 local a = getattr(pointer,a_mathalphabet) or 0 -- local g, a = getattrs(pointer,a_mathgreek,a_mathalphabet) -- if not a then a = 0 end -- if not g then g = 0 end local char, font, fam = getcharspec(pointer) local characters = fontcharacters[font] if a > 0 or g > 0 then if a > 0 then -- not really critital but we could use properties setattr(pointer,a_mathgreek,0) end if g > 0 then -- not really critital but we could use properties 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 local fnt = getfontoffamily(fam,1) setchar(pointer,errorchar(font,char)) if font ~= fnt then errorchar(fnt,char) errorchar(getfontoffamily(fam,2),char) end 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, font = getcharspec(pointer) local renderset = rendersets[attr] if renderset then local newchar = renderset[char] if newchar then 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 largefamily; also, we need to check for subtype and/or -- smallfamily 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 -- todo: use registerattribute 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,"delimiter") if delimiter then local chr, fnt, fam = getcharspec(delimiter) if chr > 0 and fnt > 0 then local data = fontdata[fnt] 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, fnt, fam = getcharspec(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,"delimiter",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,fencednoad_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 : inner but should be fenced if c_prev ~= first then setlink(first,close) end return close end local stacks = setmetatableindex("table") -- 1=open 2=close 3=middle 4=both : todo check 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 = 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) if use_math_goodies then 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 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) -- fam is always zero, so we assume a very consistent setup 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 = 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 current | attribute else return attribute end end implement { name = "presetmathfontalternate", arguments = "argument", public = true, protected = true, actions = function(tag) if texgetmode() == mathmode_code then texsetattribute(a_mathalternate,getalternate(0,tag)) end end, } implement { name = "setmathfontalternate", arguments = "argument", public = true, protected = true, actions = function(tag) if texgetmode() == mathmode_code then texsetattribute(a_mathalternate,getalternate(0,tag,texgetattribute(a_mathalternate))) end end, } 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 (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),char,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, font = getcharspec(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 first, firstfont = getcharspec(pointer) local list = hash[firstfont] if list then 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 local second, secondfont = getcharspec(pointer) if secondfont == firstfont then local kern = found[second] if kern then kern = kern * fontparameters[firstfont].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) if use_math_goodies then processnoads(head,kernpairs,"kernpairs") end return true -- not needed end end -- primes and such do -- is validpair stil needed? why not always now? local a_mathcollapsing = privateattribute("mathcollapsing") local collapse = { } local mathlists = characters.mathlists local validpair = { [ordinarynoad_code] = true, [operatornoad_code] = true, [binarynoad_code] = true, -- new [relationnoad_code] = true, [opennoad_code] = true, -- new [middlenoad_code] = true, -- new [closenoad_code] = true, -- new [punctuationnoad_code] = true, -- new [innernoad_code] = false, [fencednoad_code] = false, [undernoad_code] = false, [overnoad_code] = false, [vcenternoad_code] = false, [fractionnoad_code] = true, [radicalnoad_code] = false, [accentnoad_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 local fixscripts = { } local primes = { -- primes [0x2032] = true, [0x2033] = true, [0x2034] = true, [0x2057] = true, -- reverse primes [0x2035] = true, [0x2036] = true, [0x2037] = true, } local fixable = { [noad_code] = true, [accent_code] = true, [radical_code] = true, [fraction_code] = true, } -- [prime|sub|sup]first fixscripts[mathchar_code] = function(pointer,what,n,parent,nested) -- todo: switch to turn it on and off if parent then local char = getchar(pointer) if char and primes[char] then local nuc = getnucleus(parent) if pointer == nuc then local prev = getprev(parent) if prev and fixable[getid(prev)] then local prevsup = getsup(prev) local prevsub = getsub(prev) local primesup = getsup(parent) local primesub = getsub(parent) setfield(prev,"scriptorder",prevsub and 2 or 1) -- sub first, then prime if primesup and not prevsup then setsup(prev,primesup) primesup = nil end if primesub and not prevsub then setsub(prev,primesub) primesub = nil end setfield(prev,"prime",nuc) setnucleus(parent) if not primesup then setsup(parent) end if not primesub then setsub(parent) end if not (primesup or primesub) then setlink(prev,getnext(parent)) flushnode(parent) return true, prev, prev end end end end end end function noads.handlers.fixscripts(head,style,penalties) processnoads(head,fixscripts,"fixscripts") return true -- not needed end end -- variants (upgraded for script too) do local variants = { } local chardata = characters.data local a_variant = privateattribute("mathvariant") local function setvariant(pointer,selector,char) local tfmdata = fontdata[getfont(pointer)] local mathvariants = tfmdata.resources.variants -- and variantdata / can be a hash if mathvariants then mathvariants = mathvariants[selector] if mathvariants then local variant = mathvariants[char] 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 end end end variants[mathchar_code] = function(pointer,what,n,parent) -- also set export value local char = getchar(pointer) local data = chardata[char] if data then local variants = data.variants if variants then local next = getnext(parent) if next and getid(next) == noad_code then local nucleus = getnucleus(next) if nucleus and getid(nucleus) == mathchar_code then local selector = getchar(nucleus) if variants[selector] then setvariant(pointer,selector,char) setprev(next,pointer) setnext(parent,getnext(next)) flushnode(next) end end end local selector = getattr(pointer,a_variant) if selector and variants[selector] then setvariant(pointer,selector,char) end end end end function mathematics.addvariant(tfmdata,char,variant,selector) if char and variant and selector then local data = chardata[char] if data then local variants = data.variants if variants and variants[selector] then local resources = tfmdata.resources local variants = resources.variants -- and variantdata if not variants then variants = { } resources.variants = variants end local selectors = variants[selector] if not selectors then selectors = { } variants[selector] = selectors end selectors[char] = variant return true end end end return false end function handlers.variants(head,style,penalties) processnoads(head,variants,"unicode variant") return true -- not needed end local valid = { calligraphic = 0xFE00, calligraphy = 0xFE00, script = 0xFE01, handwriting = 0xFE01, } function mathematics.setvariant(s) texsetattribute(a_variant,valid[s] or unsetvalue) end implement { name = "setmathvariant", public = true, protected = true, arguments = "argument", actions = mathematics.setvariant, } end -- for manuals do local classes = { } local colors = { [relationnoad_code] = "trace:dr", [ordinarynoad_code] = "trace:db", [binarynoad_code] = "trace:dg", [opennoad_code] = "trace:dm", [middlenoad_code] = "trace:dm", [closenoad_code] = "trace:dm", [punctuationnoad_code] = "trace:dc", -- [operatornoad_code] = "", -- [innernoad_code = "", -- [fencednoad_code = "", -- [undernoad_code] = "", -- [overnoad_code] = "", -- [vcenternoad_code] = "", -- [fractionnoad_code] = "", -- [radicalnoad_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 do local traversehlist = nuts.traversers.hlist local getshift = nuts.getshift local setwhd = nuts.setwhd local setshift = nuts.setshift -- normalizer: can become engine feature (native tex loves shifts) local function normalize(h) for n, s in traversehlist, h do if s > 0 then local sh = getshift(n) local ox, oy = getoffsets(n) if sh ~= 0 then local w, h, d = getwhd(n) h = h - sh d = d + sh setshift(n) setwhd(n,w,h > 0 and h or 0,d > 0 and d or 0) setoffsets(n,ox,oy - sh) end end local l = getlist(l) if l then normalize(l) end end end function handlers.normalize(h) return normalize(h) end end do local traversehlist = nuts.traversers.hlist local texgetdimen = tex.getdimen local texgetglue = tex.getglue local texgetcount = tex.getcount local newrule = nuts.pool.outlinerule local newkern = nuts.pool.kern local setcolor = nodes.tracers.colors.set local a_mathsnap = attributes.private("mathsnap") function handlers.snap(h,_,_,_,_,level) -- if not level or level == 0 then if texgetcount("mathnestinglevel") == 1 then local trace_color if trace_snapping == "frame" then trace_color = "darkgray" elseif type(trace_snapping) == "string" then trace_color = trace_snapping else trace_color = false end local ht, dp, dd, hs, ds, hd for n, s in traversehlist, h do local step = getattr(n,a_mathsnap) if step then local done = false if not dd then ht = texgetdimen("mathstrutht") dp = texgetdimen("mathstrutdp") hd = ht + dp -- lineskip can be large in alignments -- dd = hd / 12 dd = hd / 6 if step == 0xFFFF then hs = dd ds = dd else hs = ht/step ds = dp/step end end local w, h, d = getwhd(n) -- snap to line ::height:: if h-dd < ht then if trace_snapping == true then report_snapping("adapting ht: old %p, new %p, lineskip %p",h,ht,dd) end done = true setheight(n,ht) goto depth end if h > ht then -- while ht < (h-dd) do while ht < h do ht = round(ht + hs) end if h ~= ht then setheight(n,ht) if trace_snapping == true then report_snapping("enlarging ht: old %p, new %p, step %p",h,ht,hs) end done = true end end ::depth:: if d-dd < dp then if trace_snapping == true then report_snapping("adapting dp: old %p, new %p, lineskip %p",d,dp,dd) end setdepth(n,dp) done = true goto done end if d > dp then -- while dp < (d-dd) do while dp < d do dp = round(dp + ds) end if d ~= dp then setdepth(n,dp) if trace_snapping == true then report_snapping("enlarging dp: old %p, new %p, step %p",d,dp,ds) end done = true end end ::done:: if done and trace_color then -- w, h, d = getwhd(n) -- local r = newrule(w,h,d,65536) -- setcolor(r,trace_color) -- setlink(r,newkern(-w),getlist(n)) -- setlist(n,r) local old = newrule(w,h,d,65536) setcolor(old,"middlegray") w, h, d = getwhd(n) local new = newrule(w,h,d,65536/4) setcolor(new,trace_color) setlink(old,newkern(-w),new,newkern(-w),getlist(n)) local ox, oy = getoffsets(n) setoffsets(old,-ox,-oy) setoffsets(new,-ox,-oy) setlist(n,old) end end end end end local valid = { [v_reset] = unsetvalue, [v_line] = 0xFFFF, [v_small] = 8, [v_medium] = 4, [v_big] = 2, } function mathematics.setsnapping(s) if not enabled then enableaction("math", "noads.handlers.snap") enabled = true end texsetattribute(a_mathsnap,valid[s] or unsetvalue) end implement { name = "setmathsnapping", public = true, protected = true, arguments = "argument", actions = mathematics.setsnapping, } 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 = ordinarynoad_code, binary = binarynoad_code, relation = relationnoad_code, punctuation = punctuationnoad_code, inner = innernoad_code, fenced = fencednoad_code, -- fraction = fractionnoad_code, -- radical = radicalnoad_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 -- -- 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 builders.kernel.mlisttohlist = mlisttohlist local actions = tasks.actions("math") -- head, style, penalties local starttiming, stoptiming = statistics.starttiming, statistics.stoptiming function processors.mlisttohlist(head,style,penalties,beginclass,endclass,level) starttiming(noads) head = actions(head,style,penalties,beginclass,endclass,level) 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)