if not modules then modules = { } end modules ['math-noa'] = { version = 1.001, comment = "companion to math-ini.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } -- beware: this is experimental code and there will be a more -- generic (attribute value driven) interface too but for the -- moment this is ok -- -- 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 local utfchar, utfbyte = utf.char, utf.byte local formatters = string.formatters local div = math.div local fonts, nodes, node, mathematics = fonts, nodes, node, mathematics local otf = fonts.handlers.otf local otffeatures = fonts.constructors.newfeatures("otf") local registerotffeature = otffeatures.register local privateattribute = attributes.private local registertracker = trackers.register local registerdirective = directives.register local logreporter = logs.reporter 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_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_families = false registertracker("math.families", function(v) trace_families = 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_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_families = logreporter("mathematics","families") 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 getfield = nuts.getfield local setfield = nuts.setfield local getnext = nuts.getnext local getprev = nuts.getprev local getid = nuts.getid local getfont = nuts.getfont local getsubtype = nuts.getsubtype local getchar = nuts.getchar local getattr = nuts.getattr local setattr = nuts.setattr local insert_node_after = nuts.insert_after local insert_node_before = nuts.insert_before local free_node = nuts.free local new_node = nuts.new -- todo: pool: math_noad math_sub local copy_node = nuts.copy local mlist_to_hlist = nodes.mlist_to_hlist local font_of_family = node.family_font local new_kern = nodepool.kern local new_rule = nodepool.rule local fonthashes = fonts.hashes local fontdata = fonthashes.identifiers local fontcharacters = fonthashes.characters local fontproperties = fonthashes.properties local fontitalics = fonthashes.italics local fontemwidths = fonthashes.emwidths local fontexheights = fonthashes.exheights local variables = interfaces.variables local texsetattribute = tex.setattribute local texgetattribute = tex.getattribute local unsetvalue = attributes.unsetvalue 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 nodecodes = nodes.nodecodes local noadcodes = nodes.noadcodes local noad_ord = noadcodes.ord local noad_rel = noadcodes.rel local noad_punct = noadcodes.punct local noad_opdisplaylimits= noadcodes.opdisplaylimits local noad_oplimits = noadcodes.oplimits local noad_opnolimits = noadcodes.opnolimits local math_noad = nodecodes.noad -- attr nucleus sub sup local math_accent = nodecodes.accent -- attr nucleus sub sup accent local math_radical = nodecodes.radical -- attr nucleus sub sup left degree local math_fraction = nodecodes.fraction -- attr nucleus sub sup left right local math_box = nodecodes.subbox -- attr list local math_sub = nodecodes.submlist -- attr list local math_char = nodecodes.mathchar -- attr fam char local math_textchar = nodecodes.mathtextchar -- attr fam char local math_delim = nodecodes.delim -- attr small_fam small_char large_fam large_char local math_style = nodecodes.style -- attr style local math_choice = nodecodes.choice -- attr display text script scriptscript local math_fence = nodecodes.fence -- attr subtype local hlist_code = nodecodes.hlist local glyph_code = nodecodes.glyph local left_fence_code = 1 local right_fence_code = 3 local function process(start,what,n,parent) if n then n = n + 1 else n = 0 end local prev = nil while start do local id = getid(start) if trace_processing then if id == math_noad then report_processing("%w%S, class %a",n*2,nutstring(start),noadcodes[getsubtype(start)]) elseif id == math_char then local char = getchar(start) local fam = getfield(start,"fam") local font = font_of_family(fam) 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 = proc(start,what,n,parent) -- prev is bugged: or getprev(start) if newstart then start = newstart -- report_processing("stop processing (new start)") else -- report_processing("stop processing") end elseif id == math_char or id == math_textchar or id == math_delim then break elseif id == math_noad then if prev then -- we have no proper prev in math nodes yet setfield(start,"prev",prev) end local noad = getfield(start,"nucleus") if noad then process(noad,what,n,start) end -- list noad = getfield(start,"sup") if noad then process(noad,what,n,start) end -- list noad = getfield(start,"sub") if noad then process(noad,what,n,start) end -- list elseif id == math_box or id == math_sub then local noad = getfield(start,"list") if noad then process(noad,what,n,start) end -- list (not getlist !) elseif id == math_fraction 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 == math_fence then local noad = getfield(start,"delim") if noad then process(noad,what,n,start) end -- delimiter elseif id == math_radical then local noad = getfield(start,"nucleus") if noad then process(noad,what,n,start) end -- list noad = getfield(start,"sup") if noad then process(noad,what,n,start) end -- list noad = getfield(start,"sub") 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,"degree") if noad then process(noad,what,n,start) end -- list elseif id == math_accent then local noad = getfield(start,"nucleus") if noad then process(noad,what,n,start) end -- list noad = getfield(start,"sup") if noad then process(noad,what,n,start) end -- list noad = getfield(start,"sub") if noad then process(noad,what,n,start) end -- list 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 == math_style then -- has a next else -- glue, penalty, etc end prev = start start = getnext(start) end end local function processnoads(head,actions,banner) if trace_processing then report_processing("start %a",banner) process(tonut(head),actions) report_processing("stop %a",banner) else process(tonut(head),actions) end end noads.process = processnoads -- local unknowns = { } local checked = { } -- simple case local tracked = false trackers.register("fonts.missing", function(v) tracked = v end) local cached = table.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 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 -- experiment (when not present fall back to fam 0) -- needs documentation -- 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 local families = { } local a_mathfamily = privateattribute("mathfamily") local boldmap = mathematics.boldmap local familymap = { [0] = "regular", "regular", "regular", "bold", "bold", "bold", "pseudobold", "pseudobold", "pseudobold", } families[math_char] = function(pointer) if getfield(pointer,"fam") == 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 setfield(pointer,"fam",newa) elseif not fontcharacters[font_of_family(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 setfield(pointer,"fam",newa-3) end else setattr(pointer,a_exportstatus,char) setfield(pointer,"char",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 setfield(pointer,"fam",newa) end else local char = getchar(pointer) if not fontcharacters[font_of_family(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 setfield(pointer,"fam",a) end end end end end families[math_delim] = 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[font_of_family(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[font_of_family(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 families[math_textchar] = families[math_char] function handlers.families(head,style,penalties) processnoads(head,families,"families") return true end -- character remapping local a_mathalphabet = privateattribute("mathalphabet") local a_mathgreek = privateattribute("mathgreek") processors.relocate = { } local function report_remap(tag,id,old,new,extra) report_remapping("remapping %s in font %s from %C to %C%s",tag,id,old,new,extra) end local remapalphabets = mathematics.remapalphabets local fallbackstyleattr = mathematics.fallbackstyleattr local setnodecolor = nodes.tracers.colors.set local function checked(pointer) local char = getchar(pointer) local fam = getfield(pointer,"fam") local id = font_of_family(fam) local tc = fontcharacters[id] if not tc[char] then local specials = characters.data[char].specials if specials and (specials[1] == "char" or specials[1] == "font") then newchar = specials[#specials] if trace_remapping then report_remap("fallback",id,char,newchar) end if trace_analyzing then setnodecolor(pointer,"font:isol") end setattr(pointer,a_exportstatus,char) -- testcase: exponentiale setfield(pointer,"char",newchar) return true end end end processors.relocate[math_char] = function(pointer) local g = getattr(pointer,a_mathgreek) or 0 local a = getattr(pointer,a_mathalphabet) or 0 local char = getchar(pointer) local fam = getfield(pointer,"fam") local font = font_of_family(fam) 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 if characters[newchar] then if trace_remapping then report_remap("char",font,char,newchar) end if trace_analyzing then setnodecolor(pointer,"font:isol") end setfield(pointer,"char",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 setfield(pointer,"char",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 end end if not characters[char] then setfield(pointer,"char",errorchar(font,char)) end if trace_analyzing then setnodecolor(pointer,"font:medi") end if check_coverage then return checked(pointer) end end processors.relocate[math_textchar] = function(pointer) if trace_analyzing then setnodecolor(pointer,"font:init") end end processors.relocate[math_delim] = function(pointer) if trace_analyzing then setnodecolor(pointer,"font:fina") end end function handlers.relocate(head,style,penalties) processnoads(head,processors.relocate,"relocate") return true end -- rendering (beware, not exported) processors.render = { } local rendersets = mathematics.renderings.numbers or { } -- store processors.render[math_char] = 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 fam = getfield(pointer,"fam") local font = font_of_family(fam) local characters = fontcharacters[font] if characters and characters[newchar] then setfield(pointer,"char",newchar) setattr(pointer,a_exportstatus,char) end end end end end function handlers.render(head,style,penalties) processnoads(head,processors.render,"render") return true 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 local mathsize = privateattribute("mathsize") local resize = { } processors.resize = resize resize[math_fence] = function(pointer) local subtype = getsubtype(pointer) if subtype == left_fence_code or subtype == right_fence_code then local a = getattr(pointer,mathsize) if a and a > 0 then local method, size = div(a,100), a % 100 setattr(pointer,mathsize,0) local delimiter = getfield(pointer,"delim") local chr = getfield(delimiter,"small_char") if chr > 0 then local fam = getfield(delimiter,"small_fam") local id = font_of_family(fam) if id > 0 then setfield(delimiter,"small_char",mathematics.big(fontdata[id],chr,size,method)) end end end end end function handlers.resize(head,style,penalties) processnoads(head,resize,"resize") return true end -- normalize scripts 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) == math_noad do local nextnucleus = getfield(next,"nucleus") if nextnucleus and getid(nextnucleus) == math_char and not getfield(next,"sub") and not getfield(next,"sup") 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) setfield(nextnucleus,"char",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) setfield(nextnucleus,"char",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 setfield(pointer,"sup",getfield(start_super,"nucleus")) else local list = new_node(math_sub) -- todo attr setfield(list,"list",start_super) setfield(pointer,"sup",list) end if mode == "super" then setfield(pointer,"next",getnext(stop_super)) end setfield(stop_super,"next",nil) end if start_sub then if start_sub == stop_sub then setfield(pointer,"sub",getfield(start_sub,"nucleus")) else local list = new_node(math_sub) -- todo attr setfield(list,"list",start_sub) setfield(pointer,"sub",list) end if mode == "sub" then setfield(pointer,"next",getnext(stop_sub)) end setfield(stop_sub,"next",nil) end -- we could return stop end unscript[math_char] = replace -- not noads as we need to recurse function handlers.unscript(head,style,penalties) processnoads(head,unscript,"unscript") -- processnoads(head,checkers,"checkers") return true end local function collected(list) if list and next(list) then local n, t = 0, { } for k, v in table.sortedpairs(list) do n = n + v t[#t+1] = 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) -- math alternates: (in xits lgf: $ABC$ $\cal ABC$ $\mathalternate{cal}\cal ABC$) -- math alternates: (in lucidanova lgf: $ABC \mathalternate{italic} ABC$) -- todo: set alternate for specific symbols local function initializemathalternates(tfmdata) local goodies = tfmdata.goodies if goodies then local shared = tfmdata.shared 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 local lastattribute, attributes = 0, { } for k, v in next, alternates do lastattribute = lastattribute + 1 v.attribute = lastattribute attributes[lastattribute] = v end shared.mathalternates = alternates -- to be checked if shared is ok here shared.mathalternatesattributes = attributes -- to be checked if shared is ok here return end end end end registerotffeature { name = "mathalternates", description = "additional math alternative shapes", initializers = { base = initializemathalternates, node = initializemathalternates, } } local getalternate = otf.getalternate local a_mathalternate = privateattribute("mathalternate") local alternate = { } -- processors.alternate = alternate function mathematics.setalternate(fam,tag) local id = font_of_family(fam) local tfmdata = fontdata[id] local mathalternates = tfmdata.shared and tfmdata.shared.mathalternates if mathalternates then local m = mathalternates[tag] texsetattribute(a_mathalternate,m and m.attribute or unsetvalue) end end alternate[math_char] = function(pointer) local a = getattr(pointer,a_mathalternate) if a and a > 0 then setattr(pointer,a_mathalternate,0) local tfmdata = fontdata[font_of_family(getfield(pointer,"fam"))] -- we can also have a famdata local mathalternatesattributes = tfmdata.shared.mathalternatesattributes if mathalternatesattributes then local what = mathalternatesattributes[a] local alt = getalternate(tfmdata,getchar(pointer),what.feature,what.value) if alt then if trace_alternates then report_alternates("alternate %a, value %a, replacing glyph %U by glyph %U", tostring(what.feature),tostring(what.value),getchar(pointer),alt) end setfield(pointer,"char",alt) end end end end function handlers.check(head,style,penalties) processnoads(head,alternate,"check") return true 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 a mess. There are all kind of assumptions based on -- old fonts and new font. 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 mxed math -- and text. local a_mathitalics = privateattribute("mathitalics") local italics = { } local default_factor = 1/20 local function getcorrection(method,font,char) -- -- or character.italic -- (this one is for tex) local correction, fromvisual if method == 1 then -- only font data triggered by fontitalics local italics = fontitalics[font] if italics then local character = fontcharacters[font][char] if character then correction = character.italic_correction if correction and correction ~= 0 then return correction, false end end end elseif method == 2 then -- only font data triggered by fontdata local character = fontcharacters[font][char] if character then correction = character.italic_correction if correction and correction ~= 0 then return correction, false end end elseif method == 3 then -- only quad based by selective local visual = chardata[char].visual if not visual then -- skip elseif visual == "it" or visual == "bi" then correction = fontproperties[font].mathitalic_defaultvalue or default_factor*fontemwidths[font] if correction and correction ~= 0 then return correction, true end end elseif method == 4 then -- combination of 1 and 3 local italics = fontitalics[font] if italics then local character = fontcharacters[font][char] if character then correction = character.italic_correction if correction and correction ~= 0 then return correction, false end end end if not correction then local visual = chardata[char].visual if not visual then -- skip elseif visual == "it" or visual == "bi" then correction = fontproperties[font].mathitalic_defaultvalue or default_factor*fontemwidths[font] if correction and correction ~= 0 then return correction, true end end end end end local setcolor = nodes.tracers.colors.set local resetcolor = nodes.tracers.colors.reset local italic_kern = new_kern local c_positive_d = "trace:db" local c_negative_d = "trace:dr" local function insert_kern(current,kern) local sub = new_node(math_sub) -- todo: pool local noad = new_node(math_noad) -- todo: pool setfield(sub,"list",kern) setfield(kern,"next",noad) setfield(noad,"nucleus",current) return sub end registertracker("math.italics.visualize", function(v) if v then italic_kern = function(k,font) local ex = 1.5 * fontexheights[font] if k > 0 then return setcolor(new_rule(k,ex,ex),c_positive_d) else -- influences un* return old_kern(k) .. setcolor(new_rule(-k,ex,ex),c_negative_d) .. old_kern(k) end end else italic_kern = new_kern end end) italics[math_char] = function(pointer,what,n,parent) local method = getattr(pointer,a_mathitalics) if method and method > 0 then local char = getchar(pointer) local font = font_of_family(getfield(pointer,"fam")) -- todo: table local correction, visual = getcorrection(method,font,char) if correction then local pid = getid(parent) local sub, sup if pid == math_noad then sup = getfield(parent,"sup") sub = getfield(parent,"sub") end if sup or sub then local subtype = getsubtype(parent) if subtype == noad_oplimits then if sup then setfield(parent,"sup",insert_kern(sup,italic_kern(correction,font))) if trace_italics then report_italics("method %a, adding %p italic correction for upper limit of %C",method,correction,char) end end if sub then local correction = - correction setfield(parent,"sub",insert_kern(sub,italic_kern(correction,font))) if trace_italics then report_italics("method %a, adding %p italic correction for lower limit of %C",method,correction,char) end end elseif sup then if pointer ~= sub then setfield(parent,"sup",insert_kern(sup,italic_kern(correction,font))) if trace_italics then report_italics("method %a, adding %p italic correction before superscript after %C",method,correction,char) end else -- otherwise we inject twice end end else 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, adding %p italic correction between %C and end math",method,correctio,char) end insert_node_after(parent,parent,italic_kern(correction,font)) end elseif getid(next_noad) == math_noad then local next_subtype = getsubtype(next_noad) if next_subtype == noad_punct or next_subtype == noad_ord then local next_nucleus = getfield(next_noad,"nucleus") if getid(next_nucleus) == math_char then local next_char = getchar(next_nucleus) local next_data = chardata[next_char] local visual = next_data.visual if visual == "it" or visual == "bi" then -- if trace_italics then -- report_italics("method %a, skipping %p italic correction between italic %C and italic %C",method,correction,char,next_char) -- end else local category = next_data.category if category == "nd" or category == "ll" or category == "lu" then if trace_italics then report_italics("method %a, adding %p italic correction between italic %C and non italic %C",method,correction,char,next_char) end insert_node_after(parent,parent,italic_kern(correction,font)) -- elseif next_data.height > (fontexheights[font]/2) then -- if trace_italics then -- report_italics("method %a, adding %p italic correction between %C and ascending %C",method,correction,char,next_char) -- end -- insert_node_after(parent,parent,italic_kern(correction,font)) -- elseif trace_italics then -- -- report_italics("method %a, skipping %p italic correction between %C and %C",method,correction,char,next_char) end end end end end end end end end function handlers.italics(head,style,penalties) processnoads(head,italics,"italics") return true end local enable enable = function() tasks.enableaction("math", "noads.handlers.italics") if trace_italics then report_italics("enabling math italics") end enable = false end -- best do this only on math mode (less overhead) function mathematics.setitalics(n) if enable then enable() end if n == variables.reset then texsetattribute(a_mathitalics,unsetvalue) else texsetattribute(a_mathitalics,tonumber(n) or unsetvalue) end end function mathematics.resetitalics() texsetattribute(a_mathitalics,unsetvalue) end -- primes and such local collapse = { } processors.collapse = collapse local mathpairs = characters.mathpairs mathpairs[0x2032] = { [0x2032] = 0x2033, [0x2033] = 0x2034, [0x2034] = 0x2057 } -- (prime,prime) (prime,doubleprime) (prime,tripleprime) mathpairs[0x2033] = { [0x2032] = 0x2034, [0x2033] = 0x2057 } -- (doubleprime,prime) (doubleprime,doubleprime) mathpairs[0x2034] = { [0x2032] = 0x2057 } -- (tripleprime,prime) mathpairs[0x2035] = { [0x2035] = 0x2036, [0x2036] = 0x2037 } -- (reversedprime,reversedprime) (reversedprime,doublereversedprime) mathpairs[0x2036] = { [0x2035] = 0x2037 } -- (doublereversedprime,reversedprime) mathpairs[0x222B] = { [0x222B] = 0x222C, [0x222C] = 0x222D } mathpairs[0x222C] = { [0x222B] = 0x222D } mathpairs[0x007C] = { [0x007C] = 0x2016, [0x2016] = 0x2980 } -- bar+bar=double bar+double=triple mathpairs[0x2016] = { [0x007C] = 0x2980 } -- double+bar=triple local movesub = { -- primes [0x2032] = 0xFE932, [0x2033] = 0xFE933, [0x2034] = 0xFE934, [0x2057] = 0xFE957, -- reverse primes [0x2035] = 0xFE935, [0x2036] = 0xFE936, [0x2037] = 0xFE937, } local validpair = { [noad_rel] = true, [noad_ord] = true, [noad_opdisplaylimits] = true, [noad_oplimits] = true, [noad_opnolimits] = true, } local function movesubscript(parent,current_nucleus,current_char) local prev = getfield(parent,"prev") if prev and getid(prev) == math_noad then if not getfield(prev,"sup") and not getfield(prev,"sub") then -- {f} {'}_n => f_n^' setfield(current_nucleus,"char",movesub[current_char or getchar(current_nucleus)]) local nucleus = getfield(parent,"nucleus") local sub = getfield(parent,"sub") local sup = getfield(parent,"sup") setfield(prev,"sup",nucleus) setfield(prev,"sub",sub) local dummy = copy_node(nucleus) setfield(dummy,"char",0) setfield(parent,"nucleus",dummy) setfield(parent,"sub",nil) if trace_collapsing then report_collapsing("fixing subscript") end elseif not getfield(prev,"sup") then -- {f} {'}_n => f_n^' setfield(current_nucleus,"char",movesub[current_char or getchar(current_nucleus)]) local nucleus = getfield(parent,"nucleus") local sup = getfield(parent,"sup") setfield(prev,"sup",nucleus) local dummy = copy_node(nucleus) setfield(dummy,"char",0) setfield(parent,"nucleus",dummy) if trace_collapsing then report_collapsing("fixing subscript") end end end end local function collapsepair(pointer,what,n,parent,nested) -- todo: switch to turn in on and off if parent then if validpair[getsubtype(parent)] then local current_nucleus = getfield(parent,"nucleus") if getid(current_nucleus) == math_char then local current_char = getchar(current_nucleus) if not getfield(parent,"sub") and not getfield(parent,"sup") then local mathpair = mathpairs[current_char] if mathpair then local next_noad = getnext(parent) if next_noad and getid(next_noad) == math_noad then if validpair[getsubtype(next_noad)] then local next_nucleus = getfield(next_noad,"nucleus") local next_char = getchar(next_nucleus) if getid(next_nucleus) == math_char then local newchar = mathpair[next_char] if newchar then local fam = getfield(current_nucleus,"fam") local id = font_of_family(fam) local characters = fontcharacters[id] if characters and characters[newchar] then if trace_collapsing then report_collapsing("%U + %U => %U",current_char,next_char,newchar) end setfield(current_nucleus,"char",newchar) local next_next_noad = getnext(next_noad) if next_next_noad then setfield(parent,"next",next_next_noad) setfield(next_next_noad,"prev",parent) else setfield(parent,"next",nil) end setfield(parent,"sup",getfield(next_noad,"sup")) setfield(parent,"sub",getfield(next_noad,"sub")) setfield(next_noad,"sup",nil) setfield(next_noad,"sub",nil) free_node(next_noad) collapsepair(pointer,what,n,parent,true) -- if not nested and movesub[current_char] then -- movesubscript(parent,current_nucleus,current_char) -- end end elseif not nested and movesub[current_char] then movesubscript(parent,current_nucleus,current_char) end end end elseif not nested and movesub[current_char] then movesubscript(parent,current_nucleus,current_char) end elseif not nested and movesub[current_char] then movesubscript(parent,current_nucleus,current_char) end elseif not nested and movesub[current_char] then movesubscript(parent,current_nucleus,current_char) end end end end end collapse[math_char] = collapsepair function noads.handlers.collapse(head,style,penalties) processnoads(head,collapse,"collapse") return true end -- variants 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[math_char] = 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) == math_noad then local nucleus = getfield(next,"nucleus") if nucleus and getid(nucleus) == math_char and getchar(nucleus) == selector then local variant local tfmdata = fontdata[font_of_family(getfield(pointer,"fam"))] -- we can also have a famdata local mathvariants = tfmdata.resources.variants -- and variantdata if mathvariants then mathvariants = mathvariants[selector] if mathvariants then variant = mathvariants[char] end end if variant then setfield(pointer,"char",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 setfield(next,"prev",pointer) setfield(parent,"next",getnext(next)) free_node(next) end end end end function handlers.variants(head,style,penalties) processnoads(head,variants,"unicode variant") return true end -- for manuals local classes = { } local colors = { [noadcodes.rel] = "trace:dr", [noadcodes.ord] = "trace:db", [noadcodes.bin] = "trace:dg", [noadcodes.open] = "trace:dm", [noadcodes.close] = "trace:dm", [noadcodes.punct] = "trace:dc", -- [noadcodes.opdisplaylimits] = "", -- [noadcodes.oplimits] = "", -- [noadcodes.opnolimits] = "", -- [noadcodes.inner = "", -- [noadcodes.under = "", -- [noadcodes.over = "", -- [noadcodes.vcenter = "", } classes[math_char] = 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 end registertracker("math.classes",function(v) tasks.setaction("math","noads.handlers.classes",v) end) -- just for me function handlers.showtree(head,style,penalties) inspect(nodes.totree(head)) end registertracker("math.showtree",function(v) tasks.setaction("math","noads.handlers.showtree",v) end) -- the normal builder function builders.kernel.mlist_to_hlist(head,style,penalties) return mlist_to_hlist(head,style,penalties), true end -- function builders.kernel.mlist_to_hlist(head,style,penalties) -- print("!!!!!!! BEFORE",penalties) -- for n in node.traverse(head) do print(n) end -- print("!!!!!!!") -- head = mlist_to_hlist(head,style,penalties) -- print("!!!!!!! AFTER") -- for n in node.traverse(head) do print(n) end -- print("!!!!!!!") -- return head, true -- end tasks.new { name = "math", arguments = 2, processor = utilities.sequencers.nodeprocessor, sequence = { "before", "normalizers", "builders", "after", }, } tasks.freezegroup("math", "normalizers") -- experimental tasks.freezegroup("math", "builders") -- experimental local actions = tasks.actions("math") -- head, style, penalties local starttiming, stoptiming = statistics.starttiming, statistics.stoptiming function processors.mlist_to_hlist(head,style,penalties) starttiming(noads) local head, done = actions(head,style,penalties) stoptiming(noads) return head, done end callbacks.register('mlist_to_hlist',processors.mlist_to_hlist,"preprocessing math list") -- tracing statistics.register("math processing time", function() return statistics.elapsedseconds(noads) end) -- interface local implement = interfaces.implement implement { name = "setmathalternate", actions = mathematics.setalternate, arguments = { "integer", "string" } } implement { name = "setmathitalics", actions = mathematics.setitalics } implement { name = "resetmathitalics", actions = mathematics.resetitalics }