if not modules then modules = { } end modules ['math-act'] = { 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" } -- Here we tweak some font properties (if needed). The commented sections -- have been removed (no longer viable) but can be found in the .lua variant. local type, next = type, next local fastcopy, insert, remove = table.fastcopy, table.insert, table.remove local formatters = string.formatters local byte = string.byte local trace_defining = false trackers.register("math.defining", function(v) trace_defining = v end) local trace_collecting = false trackers.register("math.collecting", function(v) trace_collecting = v end) local trace_tweaking = false trackers.register("math.tweaks", function(v) trace_tweaking = v end) local report_math = logs.reporter("mathematics","initializing") local report_mathtweak = logs.reporter("mathematics","tweak") local context = context local commands = commands local mathematics = mathematics local texsetdimen = tex.setdimen local abs = math.abs local helpers = fonts.helpers local upcommand = helpers.commands.up local rightcommand = helpers.commands.right local charcommand = helpers.commands.char local prependcommands = helpers.prependcommands local sequencers = utilities.sequencers local appendgroup = sequencers.appendgroup local appendaction = sequencers.appendaction local fontchars = fonts.hashes.characters local fontproperties = fonts.hashes.properties local mathfontparameteractions = sequencers.new { name = "mathparameters", arguments = "target,original", } appendgroup("mathparameters","before") -- user appendgroup("mathparameters","system") -- private appendgroup("mathparameters","after" ) -- user function fonts.constructors.assignmathparameters(original,target) -- wrong way around local runner = mathfontparameteractions.runner if runner then runner(original,target) end end function mathematics.initializeparameters(target,original) local mathparameters = original.mathparameters if mathparameters and next(mathparameters) then mathparameters = mathematics.dimensions(mathparameters) if not mathparameters.SpaceBeforeScript then mathparameters.SpaceBeforeScript = mathparameters.SpaceAfterScript end target.mathparameters = mathparameters end end sequencers.appendaction("mathparameters","system","mathematics.initializeparameters") local how = { -- RadicalKernBeforeDegree = "horizontal", -- RadicalKernAfterDegree = "horizontal", ScriptPercentScaleDown = "unscaled", ScriptScriptPercentScaleDown = "unscaled", RadicalDegreeBottomRaisePercent = "unscaled", NoLimitSupFactor = "unscaled", NoLimitSubFactor = "unscaled", } function mathematics.scaleparameters(target,original) if not target.properties.math_is_scaled then local mathparameters = target.mathparameters if mathparameters and next(mathparameters) then local parameters = target.parameters local factor = parameters.factor local hfactor = parameters.hfactor local vfactor = parameters.vfactor for name, value in next, mathparameters do local h = how[name] if h == "unscaled" then -- kept elseif h == "horizontal" then value = value * hfactor elseif h == "vertical"then value = value * vfactor else value = value * factor end mathparameters[name] = value end end target.properties.math_is_scaled = true end end -- AccentBaseHeight vs FlattenedAccentBaseHeight function mathematics.checkaccentbaseheight(target,original) local mathparameters = target.mathparameters if mathparameters and mathparameters.AccentBaseHeight == 0 then mathparameters.AccentBaseHeight = target.parameters.x_height -- needs checking end end function mathematics.checkprivateparameters(target,original) local mathparameters = target.mathparameters if mathparameters then local parameters = target.parameters local properties = target.properties if parameters then local size = parameters.size if size then if not mathparameters.FractionDelimiterSize then mathparameters.FractionDelimiterSize = 1.01 * size end if not mathparameters.FractionDelimiterDisplayStyleSize then mathparameters.FractionDelimiterDisplayStyleSize = 2.40 * size end elseif properties then report_math("invalid parameters in font %a",properties.fullname or "?") else report_math("invalid parameters in font") end elseif properties then report_math("no parameters in font %a",properties.fullname or "?") else report_math("no parameters and properties in font") end end end function mathematics.overloadparameters(target,original) local mathparameters = target.mathparameters if mathparameters and next(mathparameters) then local goodies = target.goodies if goodies then for i=1,#goodies do local goodie = goodies[i] local mathematics = goodie.mathematics local parameters = mathematics and mathematics.parameters if parameters then if trace_defining then report_math("overloading math parameters in %a @ %p",target.properties.fullname,target.parameters.size) end for name, value in next, parameters do local tvalue = type(value) if tvalue == "string" then report_math("comment for math parameter %a: %s",name,value) else local oldvalue = mathparameters[name] local newvalue = oldvalue if oldvalue then if tvalue == "number" then newvalue = value elseif tvalue == "function" then newvalue = value(oldvalue,target,original) elseif not tvalue then newvalue = nil end if trace_defining and oldvalue ~= newvalue then report_math("overloading math parameter %a: %S => %S",name,oldvalue,newvalue) end else report_math("invalid math parameter %a",name) end mathparameters[name] = newvalue end end end end end end end -- a couple of predefined tweaks: local mathtweaks = { } mathematics.tweaks = mathtweaks local function report_tweak(fmt,target,original,...) local metadata = original.shared.rawdata.metadata local parameters = target.parameters report_mathtweak( "%a, size %p, math size %i, %s", metadata and metadata.fontname or "unknown", parameters.size or 655360, parameters.mathsize or 1, string.formatters[fmt](...) ) end do local stepper = utilities.parsers.stepper local function adapt(target,original,targetcharacters,originalcharacters,k,v,compact,n) local character = targetcharacters[k] if character then local width = character.width local italic = character.italic local offsetfactor = v[1] or 1 local widthfactor = v[2] or 1 local italicfactor = v[3] or 1 if width then character.advance = width -- so advance is oldwidth character.xoffset = offsetfactor * width character.width = widthfactor * width end if italic then character.italic = italicfactor * italic elseif width and italicfactor ~= 1 then character.italic = italicfactor * width end if trace_tweaking then report_tweak("adapting dimensions of %U ",target,original,k) end local smaller = originalcharacters[k].smaller if compact and smaller and smaller ~= k then adapt(target,original,targetcharacters,originalcharacters,smaller,v,compact,n+1) end else report_math("no character %U",k) end end function mathtweaks.dimensions(target,original,parameters) local list = parameters.list if list then local targetcharacters = target.characters local originalcharacters = original.characters local compact = target.parameters.textscale and true or false for k, v in next, list do local t = type(k) if t == "number" then adapt(target,original,targetcharacters,originalcharacters,k,v,compact,1) elseif t == "string" then stepper(k,function(n) adapt(target,original,targetcharacters,originalcharacters,n,v,compact,1) end) end end end end end do local list = { { 0x2032, true, }, { 0x2033, true, 0x2032, 2 }, { 0x2034, true, 0x2032, 3 }, { 0x2057, true, 0x2032, 4 }, { 0x2035, false, }, { 0x2036, false, 0x2035, 2 }, { 0x2037, false, 0x2035, 3 }, } local function fix(target,original,characters,factor,scale,newheight,code,mandate) local char = characters[code] if char then local oldheight = scale * char.height char.yoffset = newheight - oldheight char.height = newheight if scale ~= 1 then char.width = scale * char.width char.commands = { { "slot", 0, code, scale } } end if trace_tweaking then report_tweak("fixing prime %U ",target,original,code) end local small = char.smaller if small then fix(target,original,characters,factor,scale,newheight,small,false) end elseif mandate then report_math("missing %U prime in font %a",code,original.properties.fullname) end end function mathtweaks.fixprimes(target,original,parameters) local characters = target.characters local factor = parameters.factor or 0.85 local scale = parameters.scale or 1 local newheight = factor * target.mathparameters.AccentBaseHeight for i=1,#list do local entry = list[i] fix(target,original,characters,factor,scale,newheight,entry[1],entry[2]) end end local function fake(target,original,characters,factor,n,single,multiple) local csingle = characters[single] local cmultiple = characters[multiple] local width = csingle.width local hoffset = factor * width cmultiple.width = width + (n - 1) * hoffset cmultiple.commands = { { "offset", 0, 0, single }, { "offset", hoffset, 0, single }, n > 2 and { "offset", 2 * hoffset, 0, single } or nil, n > 3 and { "offset", 3 * hoffset, 0, single } or nil, } if trace_tweaking then report_tweak("faking %U with %i primes",target,original,multiple,n) end local ssingle = csingle.smaller local smultiple = cmultiple.smaller if ssingle and smultiple then fake(target,original,characters,factor,n,ssingle,smultiple) else --weird font end end function mathtweaks.fakeprimes(target,original,parameters) local characters = target.characters local factor = parameters.factor or .75 for i=1,#list do local entry = list[i] local n = entry[4] if n then fake(target,original,characters,factor,n,entry[3],entry[1]) end end end end function mathtweaks.action(target,original,parameters) local action = parameters.action if type(action) == "function" then action(target,original,parameters) end end do local list = { { 0x00A0, "s", 1 }, -- nbsp { 0x2000, "q", 1/2 }, -- enquad { 0x2001, "q", 1 }, -- emquad { 0x2002, "q", 1/2 }, -- enspace { 0x2003, "q", 1 }, -- emspace { 0x2004, "q", 1/3 }, -- threeperemspace { 0x2005, "q", 1/4 }, -- fourperemspace { 0x2006, "q", 1/6 }, -- sixperemspace { 0x2007, "c", byte('0') }, -- figurespace { 0x2008, "c", byte('.') }, -- punctuationspace { 0x2009, "q", 1/8 }, -- breakablethinspace { 0x200A, "q", 1/8 }, -- hairspace { 0x200B, "q", 0 }, -- zerowidthspace { 0x202F, "q", 1/8 }, -- narrownobreakspace { 0x205F, "s", 1/2 }, -- math thinspace } function mathtweaks.checkspacing(target,original,parameters) local characters = target.characters local parameters = target.parameters for i=1,#list do local entry = list[i] local unicode = entry[1] local data = characters[unicode] if not data then local method = entry[2] local fraction = entry[3] local width = 0 if how == "c" then width = characters[fraction].width -- char elseif how == "s" then width = fraction * parameters.space -- space else width = fraction * parameters.quad -- quad end if trace_tweaking then report_tweak("setting width of %U to %p",target,original,unicode,width) end characters[unicode] = { width = width } end end end end do local list = { 0x221A, } local function fix(target,original,characters,unicode) local data = characters[unicode] if data then local height = data.height or 0 local depth = data.depth or 0 if depth > height then if trace_tweaking then report_tweak("swapping height and depth of radical %U",target,original,unicode) end data.height = depth data.depth = height data.yoffset = depth - height end local small = data.smaller if small then fix(target,original,characters,small) end end end function mathtweaks.fixradicals(target,original,parameters) local characters = target.characters for i=1,#list do local unicode = list[i] fix(target,original,characters,unicode) end end end do -- For Ton, who needs the high minus and plus for calculator signs in Dutch -- school math books. local list = { { 0x207A, 0x002B, true }, { 0x207B, 0x2212, true }, { 0x208A, 0x002B, false }, { 0x208B, 0x2212, false }, } local function add(target,original,characters,unicode,template,super,baseheight,scale) if not characters[unicode] then local origdata = characters[template] if origdata then local width = scale * (origdata.width or 0) local height = scale * (origdata.height or 0) local depth = scale * (origdata.depth or 0) local half = - (height + depth) / 2 local offset = super and baseheight/2 or -baseheight/4 characters[unicode] = { width = width, height = height + offset, depth = depth - offset, yoffset = offset, unicode = unicode, -- index = origdata.index, -- scale = scale, commands = { -- { "up", yoffset }, { "slot", 0, template, scale } }, } if trace_tweaking then report_tweak("adding script %U scaled %0.3f",target,original,unicode,scale) end -- no need for smaller end end end function mathtweaks.addscripts(target,original,parameters) local characters = target.characters local baseheight = target.mathparameters.AccentBaseHeight local scaledown = parameters.scale or target.mathparameters.ScriptScriptPercentScaleDown / 100 for i=1,#list do local entry = list[i] if entry then add(target,original,characters,entry[1],entry[2],entry[3],baseheight,scaledown) end end end end do function mathtweaks.version(target,original,parameters) local metadata = original.shared.rawdata.metadata if metadata then local version = metadata.version if version then local expected = parameters.expected -- version = tonumber(string.match(version,"%d+.%d+")) if version ~= expected then report_tweak("version %a found, version %a expected",target,original,version,expected) reported[fontname] = true end end end end end local function applytweaks(when,target,original) local goodies = original.goodies if goodies then for i=1,#goodies do local goodie = goodies[i] local mathematics = goodie.mathematics local tweaks = mathematics and mathematics.tweaks if type(tweaks) == "table" then tweaks = tweaks[when] if type(tweaks) == "table" then if trace_defining then report_math("tweaking math of %a @ %p (%s)",target.properties.fullname,target.parameters.size,when) end for i=1,#tweaks do local tweak = tweaks[i] local tvalue = type(tweak) if type(tweak) == "table" then local action = mathtweaks[tweak.tweak or ""] if action then action(target,original,tweak) end end end end end end end end function mathematics.tweakbeforecopyingfont(target,original) local mathparameters = target.mathparameters -- why not hasmath if mathparameters then applytweaks("beforecopying",target,original) end end function mathematics.tweakaftercopyingfont(target,original) local mathparameters = target.mathparameters -- why not hasmath if mathparameters then applytweaks("aftercopying",target,original) end end sequencers.appendaction("mathparameters","system","mathematics.scaleparameters") sequencers.appendaction("mathparameters","system","mathematics.checkaccentbaseheight") -- should go in lfg instead sequencers.appendaction("mathparameters","system","mathematics.checkprivateparameters") -- after scaling ! sequencers.appendaction("mathparameters","system","mathematics.overloadparameters") sequencers.appendaction("beforecopyingcharacters","system","mathematics.tweakbeforecopyingfont") sequencers.appendaction("aftercopyingcharacters", "system","mathematics.tweakaftercopyingfont") -- no, it's a feature now (see good-mth): -- -- sequencers.appendaction("aftercopyingcharacters", "system","mathematics.overloaddimensions") -- helpers local setmetatableindex = table.setmetatableindex local getfontoffamily = tex.getfontoffamily local fontcharacters = fonts.hashes.characters local extensibles = utilities.storage.allocate() fonts.hashes.extensibles = extensibles local chardata = characters.data local extensibles = mathematics.extensibles -- we use numbers at the tex end (otherwise we could stick to chars) local e_left = extensibles.left local e_right = extensibles.right local e_horizontal = extensibles.horizontal local e_mixed = extensibles.mixed local e_unknown = extensibles.unknown local unknown = { e_unknown, false, false } local function extensiblecode(font,unicode) local characters = fontcharacters[font] local character = characters[unicode] if not character then return unknown end local first = character.next local code = unicode local next = first while next do code = next character = characters[next] next = character.next end local char = chardata[unicode] if not char then return unknown end if character.horiz_variants then if character.vert_variants then return { e_mixed, code, character } else local m = char.mathextensible local e = m and extensibles[m] return e and { e, code, character } or unknown end elseif character.vert_variants then local m = char.mathextensible local e = m and extensibles[m] return e and { e, code, character } or unknown elseif first then -- assume accent (they seldom stretch .. sizes) local m = char.mathextensible or char.mathstretch local e = m and extensibles[m] return e and { e, code, character } or unknown else return unknown end end setmetatableindex(extensibles,function(extensibles,font) local codes = { } setmetatableindex(codes, function(codes,unicode) local status = extensiblecode(font,unicode) codes[unicode] = status return status end) extensibles[font] = codes return codes end) local function extensiblecode(family,unicode) return extensibles[getfontoffamily(family or 0)][unicode][1] end -- left : [head] ... -- right : ... [head] -- horizontal : [head] ... [head] -- -- abs(right["start"] - right["end"]) | right.advance | characters[right.glyph].width local function horizontalcode(family,unicode) local font = getfontoffamily(family or 0) local data = extensibles[font][unicode] local kind = data[1] local loffset = 0 local roffset = 0 if kind == e_left then local charlist = data[3].horiz_variants if charlist then local left = charlist[1] loffset = abs((left["start"] or 0) - (left["end"] or 0)) end elseif kind == e_right then local charlist = data[3].horiz_variants if charlist then local right = charlist[#charlist] roffset = abs((right["start"] or 0) - (right["end"] or 0)) end elseif kind == e_horizontal then local charlist = data[3].horiz_variants if charlist then local left = charlist[1] local right = charlist[#charlist] loffset = abs((left ["start"] or 0) - (left ["end"] or 0)) roffset = abs((right["start"] or 0) - (right["end"] or 0)) end end return kind, loffset, roffset end mathematics.extensiblecode = extensiblecode mathematics.horizontalcode = horizontalcode interfaces.implement { -- can be public with two times "integerargument" name = "extensiblecode", arguments = { "integer", "integer" }, actions = { extensiblecode, context } } interfaces.implement { -- can be public with two times "integerargument" name = "horizontalcode", arguments = { "integer", "integer" }, actions = function(family,unicode) local kind, loffset, roffset = horizontalcode(family,unicode) texsetdimen("scratchleftoffset", loffset) texsetdimen("scratchrightoffset",roffset) context(kind) end } local stack = { } function mathematics.registerfallbackid(n,id,name) if trace_collecting then report_math("resolved fallback font %i, name %a, id %a, used %a", n,name,id,fontproperties[id].fontname) end stack[#stack][n] = id end interfaces.implement { -- will be shared with text name = "registerfontfallbackid", arguments = { "integer", "integer", "string" }, actions = mathematics.registerfallbackid, } function mathematics.resolvefallbacks(target,specification,fallbacks) local definitions = fonts.collections.definitions[fallbacks] if definitions then local size = specification.size -- target.size local list = { } insert(stack,list) context.pushcatcodes("prt") -- context.unprotect() for i=1,#definitions do local definition = definitions[i] local name = definition.font local features = definition.features or "" local size = size * (definition.rscale or 1) context.font_fallbacks_register_math(i,name,features,size) if trace_collecting then report_math("registering fallback font %i, name %a, size %a, features %a",i,name,size,features) end end context.popcatcodes() end end function mathematics.finishfallbacks(target,specification,fallbacks) local list = remove(stack) if list and #list > 0 then local definitions = fonts.collections.definitions[fallbacks] if definitions and #definitions > 0 then if trace_collecting then report_math("adding fallback characters to font %a",specification.hash) end local definedfont = fonts.definers.internal local copiedglyph = fonts.handlers.vf.math.copy_glyph local fonts = target.fonts local size = specification.size -- target.size local characters = target.characters if not fonts then fonts = { } target.fonts = fonts end if #fonts == 0 then fonts[1] = { id = 0, size = size } -- self, will be resolved later end local done = { } for i=1,#definitions do local definition = definitions[i] local name = definition.font local start = definition.start local stop = definition.stop local gaps = definition.gaps local check = definition.check local force = definition.force local rscale = definition.rscale or 1 local offset = definition.offset or start local id = list[i] if id then local index = #fonts + 1 fonts[index] = { id = id, size = size } local chars = fontchars[id] local function remap(unic,unicode,gap) if check and not chars[unicode] then return end if force or (not done[unic] and not characters[unic]) then if trace_collecting then report_math("replacing math character %C by %C using vector %a and font id %a for %a%s%s", unic,unicode,fallbacks,id,fontproperties[id].fontname,check and ", checked",gap and ", gap plugged") end characters[unic] = copiedglyph(target,characters,chars,unicode,index) done[unic] = true end end local step = offset - start for unicode = start, stop do remap(unicode + step,unicode,false) end if gaps then for unic, unicode in next, gaps do remap(unic,unicode,true) remap(unicode,unicode,true) end end end end elseif trace_collecting then report_math("no fallback characters added to font %a",specification.hash) end end end