if not modules then modules = { } end modules ['math-fbk'] = { 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" } local trace_fallbacks = false trackers.register("math.fallbacks", function(v) trace_fallbacks = v end) local report_fallbacks = logs.reporter("math","fallbacks") local formatters = string.formatters local fastcopy = table.fastcopy local fallbacks = { } mathematics.fallbacks = fallbacks local virtualcharacters = { } local identifiers = fonts.hashes.identifiers local lastmathids = fonts.hashes.lastmathids local tounicode16 = fonts.mappings.tounicode16 -- we need a trick (todo): if we define scriptscript, script and text in -- that order we could use their id's .. i.e. we could always add a font -- table with those id's .. in fact, we could also add a whole lot more -- as it doesn't hurt -- -- todo: use index 'true when luatex provides that feature (on the agenda) -- to be considered: -- -- in luatex provide reserve_id (and pass id as field of tfmdata) -- in context define three sizes but pass them later i.e. do virtualize afterwards function fallbacks.apply(target,original) local mathparameters = target.mathparameters -- why not hasmath if mathparameters then local characters = target.characters local parameters = target.parameters local mathsize = parameters.mathsize local size = parameters.size local usedfonts = target.fonts if not usedfonts then usedfonts = { } target.fonts = usedfonts end -- This is not okay yet ... we have no proper way to refer to 'self' -- otherwise I will make my own id allocator). local self = #usedfonts == 0 and font.nextid() or nil -- will be true local textid, scriptid, scriptscriptid local textindex, scriptindex, scriptscriptindex local textdata, scriptdata, scriptscriptdata if mathsize == 3 then -- scriptscriptsize -- textid = nil -- self -- scriptid = nil -- no smaller -- scriptscriptid = nil -- no smaller textid = self scriptid = self scriptscriptid = self elseif mathsize == 2 then -- scriptsize -- textid = nil -- self textid = self scriptid = lastmathids[3] scriptscriptid = lastmathids[3] else -- textsize -- textid = nil -- self textid = self scriptid = lastmathids[2] scriptscriptid = lastmathids[3] end if textid then textindex = #usedfonts + 1 usedfonts[textindex] = { id = textid } -- textdata = identifiers[textid] or target textdata = target else textdata = target end if scriptid then scriptindex = #usedfonts + 1 usedfonts[scriptindex] = { id = scriptid } scriptdata = identifiers[scriptid] else scriptindex = textindex scriptdata = textdata end if scriptscriptid then scriptscriptindex = #usedfonts + 1 usedfonts[scriptscriptindex] = { id = scriptscriptid } scriptscriptdata = identifiers[scriptscriptid] else scriptscriptindex = scriptindex scriptscriptdata = scriptdata end -- report_fallbacks("used textid: %S, used script id: %S, used scriptscript id: %S",textid,scriptid,scriptscriptid) local data = { textdata = textdata, scriptdata = scriptdata, scriptscriptdata = scriptscriptdata, textindex = textindex, scriptindex = scriptindex, scriptscriptindex = scriptscriptindex, textid = textid, scriptid = scriptid, scriptscriptid = scriptscriptid, characters = characters, unicode = k, target = target, original = original, size = size, mathsize = mathsize, } target.mathrelation = data -- inspect(usedfonts) for k, v in next, virtualcharacters do if not characters[k] then local tv = type(v) local cd = nil if tv == "table" then cd = v elseif tv == "number" then cd = characters[v] elseif tv == "function" then cd = v(data) end if cd then characters[k] = cd else -- something else end if trace_fallbacks and characters[k] then report_fallbacks("extending math font %a with %U",target.properties.fullname,k) end end end data.unicode = nil end end utilities.sequencers.appendaction("aftercopyingcharacters","system","mathematics.fallbacks.apply") function fallbacks.install(unicode,value) virtualcharacters[unicode] = value end -- a few examples: local function reference(index,char) if index then return { "slot", index, char } else return { "char", char } end end local function raised(data,down) local replacement = data.replacement local character = data.scriptdata.characters[replacement] if character then return { width = character.width, height = character.height, depth = character.depth, commands = { { "down", down and data.size/4 or -data.size/2 }, -- maybe exheight reference(data.scriptindex,replacement) } } end end -- virtualcharacters[0x207A] = 0x2212 -- virtualcharacters[0x207B] = 0x002B -- virtualcharacters[0x208A] = 0x2212 -- virtualcharacters[0x208B] = 0x002B virtualcharacters[0x207A] = function(data) data.replacement = 0x2212 return raised(data) end virtualcharacters[0x207B] = function(data) data.replacement = 0x002B return raised(data) end virtualcharacters[0x208A] = function(data) data.replacement = 0x2212 return raised(data,true) end virtualcharacters[0x208B] = function(data) data.replacement = 0x002B return raised(data,true) end -- local function repeated(data,char,n,fraction) -- local character = data.characters[char] -- if character then -- local width = character.width -- local delta = width - character.italic -- width * fraction -- local c = { "char", char } -- local r = { "right", right } -- local commands = { } -- for i=1,n-1 do -- width = width + delta -- commands[#commands+1] = c -- commands[#commands+1] = -delta -- end -- commands[#commands+1] = c -- return { -- width = width, -- height = character.height, -- depth = character.depth, -- commands = commands, -- } -- end -- end -- virtualcharacters[0x222C] = function(data) -- return repeated(data,0x222B,2,1/8) -- end -- virtualcharacters[0x222D] = function(data) -- return repeated(data,0x222B,3,1/8) -- end local addextra = mathematics.extras.add addextra(0xFE350, { category="sm", description="MATHEMATICAL DOUBLE ARROW LEFT END", mathclass="relation", mathname="ctxdoublearrowfillleftend", unicodeslot=0xFE350, } ) addextra(0xFE351, { category="sm", description="MATHEMATICAL DOUBLE ARROW MIDDLE PART", mathclass="relation", mathname="ctxdoublearrowfillmiddlepart", unicodeslot=0xFE351, } ) addextra(0xFE352, { category="sm", description="MATHEMATICAL DOUBLE ARROW RIGHT END", mathclass="relation", mathname="ctxdoublearrowfillrightend", unicodeslot=0xFE352, } ) local push = { "push" } local pop = { "pop" } local leftarrow = { "char", 0x2190 } local relbar = { "char", 0x2212 } local rightarrow = { "char", 0x2192 } virtualcharacters[0xFE350] = function(data) -- return combined(data,0x2190,0x2212) -- leftarrow relbar local charone = data.characters[0x2190] local chartwo = data.characters[0x2212] if charone and chartwo then local size = data.size/2 return { width = chartwo.width, height = size, depth = size, commands = { push, { "down", size/2 }, leftarrow, pop, { "down", -size/2 }, relbar, } } end end virtualcharacters[0xFE351] = function(data) -- return combined(data,0x2212,0x2212) -- relbar, relbar (isn't that just equal) local char = data.characters[0x2212] if char then local size = data.size/2 return { width = char.width, height = size, depth = size, commands = { push, { "down", size/2 }, relbar, pop, { "down", -size/2 }, relbar, } } end end virtualcharacters[0xFE352] = function(data) -- return combined(data,0x2192,0x2212) -- rightarrow relbar local charone = data.characters[0x2192] local chartwo = data.characters[0x2212] if charone and chartwo then local size = data.size/2 return { width = chartwo.width, height = size, depth = size, commands = { push, { "down", size/2 }, relbar, pop, { "right", chartwo.width - charone.width }, { "down", -size/2 }, rightarrow, } } end end -- we could move the defs from math-act here local function accent_to_extensible(target,newchr,original,oldchr,height,depth,swap,offset) local characters = target.characters local olddata = characters[oldchr] -- brrr ... pagella has only next if olddata and not olddata.commands and olddata.width > 0 then local addprivate = fonts.helpers.addprivate if swap then swap = characters[swap] height = swap.depth depth = 0 else height = height or 0 depth = depth or 0 end local correction = swap and { "down", (olddata.height or 0) - height } or { "down", olddata.height + (offset or 0)} local newdata = { commands = { correction, { "slot", 1, oldchr } }, width = olddata.width, height = height, depth = depth, } local glyphdata = newdata local nextglyph = olddata.next while nextglyph do local oldnextdata = characters[nextglyph] if oldnextdata then local newnextdata = { commands = { correction, { "slot", 1, nextglyph } }, width = oldnextdata.width, height = height, depth = depth, } local newnextglyph = addprivate(target,formatters["M-N-%H"](nextglyph),newnextdata) newdata.next = newnextglyph local nextnextglyph = oldnextdata.next if nextnextglyph == nextglyph then break else olddata = oldnextdata newdata = newnextdata nextglyph = nextnextglyph end else report_fallbacks("error in fallback: no valid next, slot %X",nextglyph) break end end local hv = olddata.horiz_variants if hv then hv = fastcopy(hv) newdata.horiz_variants = hv for i=1,#hv do local hvi = hv[i] local oldglyph = hvi.glyph local olddata = characters[oldglyph] if olddata then local newdata = { commands = { correction, { "slot", 1, oldglyph } }, width = olddata.width, height = height, depth = depth, } hvi.glyph = addprivate(target,formatters["M-H-%H"](oldglyph),newdata) else report_fallbacks("error in fallback: no valid horiz_variants, slot %X, index %i",oldglyph,i) end end end return glyphdata else return olddata end end virtualcharacters[0x203E] = function(data) -- could be FE33E instead local target = data.target local height, depth = 0, 0 local mathparameters = target.mathparameters if mathparameters then height = mathparameters.OverbarVerticalGap depth = mathparameters.UnderbarVerticalGap else height = target.parameters.xheight/4 depth = height end return accent_to_extensible(target,0x203E,data.original,0x0305,height,depth) end virtualcharacters[0xFE33E] = virtualcharacters[0x203E] -- convenient virtualcharacters[0xFE33F] = virtualcharacters[0x203E] -- convenient local function smashed(data,unicode,swap,private) local target = data.target local original = data.original local chardata = target.characters[unicode] if chardata and chardata.height > target.parameters.xheight then return accent_to_extensible(target,private,original,unicode,0,0,swap) else return original.characters[unicode] end end addextra(0xFE3DE, { description="EXTENSIBLE OF 0x03DE", unicodeslot=0xFE3DE, mathextensible = "r", mathstretch = "h" } ) addextra(0xFE3DC, { description="EXTENSIBLE OF 0x03DC", unicodeslot=0xFE3DC, mathextensible = "r", mathstretch = "h" } ) addextra(0xFE3B4, { description="EXTENSIBLE OF 0x03B4", unicodeslot=0xFE3B4, mathextensible = "r", mathstretch = "h" } ) virtualcharacters[0xFE3DE] = function(data) return smashed(data,0x23DE,0x23DF,0xFE3DE) end virtualcharacters[0xFE3DC] = function(data) return smashed(data,0x23DC,0x23DD,0xFE3DC) end virtualcharacters[0xFE3B4] = function(data) return smashed(data,0x23B4,0x23B5,0xFE3B4) end addextra(0xFE3DF, { description="EXTENSIBLE OF 0x03DF", unicodeslot=0xFE3DF, mathextensible = "r", mathstretch = "h" } ) addextra(0xFE3DD, { description="EXTENSIBLE OF 0x03DD", unicodeslot=0xFE3DD, mathextensible = "r", mathstretch = "h" } ) addextra(0xFE3B5, { description="EXTENSIBLE OF 0x03B5", unicodeslot=0xFE3B5, mathextensible = "r", mathstretch = "h" } ) virtualcharacters[0xFE3DF] = function(data) return data.target.characters[0x23DF] end virtualcharacters[0xFE3DD] = function(data) return data.target.characters[0x23DD] end virtualcharacters[0xFE3B5] = function(data) return data.target.characters[0x23B5] end -- todo: add some more .. numbers might change addextra(0xFE302, { description="EXTENSIBLE OF 0x0302", unicodeslot=0xFE302, mathstretch = "h" } ) addextra(0xFE303, { description="EXTENSIBLE OF 0x0303", unicodeslot=0xFE303, mathstretch = "h" } ) local function smashed(data,unicode,private) local target = data.target local height = target.parameters.xheight / 2 local c = accent_to_extensible(target,private,data.original,unicode,height,0,nil,-height) c.top_accent = nil return c end virtualcharacters[0xFE302] = function(data) return smashed(data,0x0302,0xFE302) end virtualcharacters[0xFE303] = function(data) return smashed(data,0x0303,0xFE303) end -- another crazy hack .. doesn't work as we define scrscr first .. we now have smaller -- primes so we have smaller primes for the moment, big ones will become an option local function smashed(data,unicode,optional) local oldchar = data.characters[unicode] if oldchar then local height = 1.2 * data.target.parameters.xheight local newchar = { commands = { { "down", oldchar.height - height }, { "char", unicode }, }, height = height, width = oldchar.width, } return newchar elseif not optional then report_fallbacks("missing %U prime in font %a",unicode,data.target.properties.fullname) end end addextra(0xFE932, { description="SMASHED PRIME 0x02032", unicodeslot=0xFE932 } ) addextra(0xFE933, { description="SMASHED PRIME 0x02033", unicodeslot=0xFE933 } ) addextra(0xFE934, { description="SMASHED PRIME 0x02034", unicodeslot=0xFE934 } ) addextra(0xFE957, { description="SMASHED PRIME 0x02057", unicodeslot=0xFE957 } ) addextra(0xFE935, { description="SMASHED BACKWARD PRIME 0x02035", unicodeslot=0xFE935 } ) addextra(0xFE936, { description="SMASHED BACKWARD PRIME 0x02036", unicodeslot=0xFE936 } ) addextra(0xFE937, { description="SMASHED BACKWARD PRIME 0x02037", unicodeslot=0xFE937 } ) virtualcharacters[0xFE932] = function(data) return smashed(data,0x02032) end virtualcharacters[0xFE933] = function(data) return smashed(data,0x02033) end virtualcharacters[0xFE934] = function(data) return smashed(data,0x02034) end virtualcharacters[0xFE957] = function(data) return smashed(data,0x02057) end virtualcharacters[0xFE935] = function(data) return smashed(data,0x02035,true) end virtualcharacters[0xFE936] = function(data) return smashed(data,0x02036,true) end virtualcharacters[0xFE937] = function(data) return smashed(data,0x02037,true) end -- actuarian (beware: xits has an ugly one) addextra(0xFE940, { category = "mn", description="SMALL ANNUITY SYMBOL", unicodeslot=0xFE940, mathclass="topaccent", mathname="smallactuarial" }) local function actuarian(data) local characters = data.target.characters local parameters = data.target.parameters local basechar = characters[0x0078] -- x (0x0058 X) local linewidth = parameters.xheight / 10 local basewidth = basechar.width local baseheight = basechar.height return { -- todo: add alttext -- compromise: lm has large hooks e.g. \actuarial{a} width = basewidth + 4 * linewidth, tounicode = tounicode16(0x20E7), commands = { { "right", 2 * linewidth }, { "down", - baseheight - 3 * linewidth }, { "rule", linewidth, basewidth + 4 * linewidth }, { "right", -linewidth }, { "down", baseheight + 4 * linewidth }, { "rule", baseheight + 5 * linewidth, linewidth }, }, } end virtualcharacters[0x020E7] = actuarian -- checked virtualcharacters[0xFE940] = actuarian -- unchecked