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, tonumber = type, next, tonumber local fastcopy, copytable, insert, remove = table.fastcopy, table.copy, table.insert, table.remove local formatters = string.formatters local byte = string.byte local setmetatableindex, sortedhash = table.setmetatableindex, table.sortedhash local lpegmatch = lpeg.match 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 blocks = characters.blocks local stepper = utilities.parsers.stepper local helpers = fonts.helpers local upcommand = helpers.commands.up local downcommand = helpers.commands.down local rightcommand = helpers.commands.right local leftcommand = helpers.commands.left 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 mathgaps = mathematics.gaps local use_math_goodies = true directives.register("math.nogoodies", function(v) use_math_goodies = not v end) 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 local undefined = 0x3FFFFFFF -- maxdimen or undefined_math_parameter function mathematics.initializeparameters(target,original) local mathparameters = original.mathparameters if mathparameters and next(mathparameters) then mathparameters = mathematics.dimensions(mathparameters) -- if not mathparameters.MinConnectorOverlap then mathparameters.MinConnectorOverlap = undefined end if not mathparameters.SubscriptShiftDownWithSuperscript then mathparameters.SubscriptShiftDownWithSuperscript = undefined end -- a tex one if not mathparameters.FractionDelimiterSize then mathparameters.FractionDelimiterSize = undefined end if not mathparameters.FractionDelimiterDisplayStyleSize then mathparameters.FractionDelimiterDisplayStyleSize = undefined end if not mathparameters.SkewedDelimiterTolerance then mathparameters.SkewedDelimiterTolerance = undefined end -- some more can be undefined: if not mathparameters.PrimeRaisePercent then mathparameters.PrimeRaisePercent = 50 end if not mathparameters.PrimeRaiseComposedPercent then mathparameters.PrimeRaiseComposedPercent = 25 end if not mathparameters.PrimeShiftUp then mathparameters.PrimeShiftUp = mathparameters.SuperscriptShiftUp end if not mathparameters.PrimeBaselineDropMax then mathparameters.PrimeBaselineDropMax = mathparameters.SuperscriptBaselineDropMax end if not mathparameters.PrimeShiftUpCramped then mathparameters.PrimeShiftUpCramped = mathparameters.SuperscriptShiftUpCramped end if not mathparameters.PrimeSpaceAfter then mathparameters.PrimeSpaceAfter = 0 end if not mathparameters.PrimeWidthPercent then mathparameters.PrimeWidthPercent = 50 end if not mathparameters.SpaceBeforeScript then mathparameters.SpaceBeforeScript = mathparameters.SpaceAfterScript end if not mathparameters.NoLimitSupFactor then mathparameters.NoLimitSupFactor = 0 end if not mathparameters.NoLimitSubFactor then mathparameters.NoLimitSubFactor = 0 end if not mathparameters.AccentTopShiftUp then mathparameters.AccentTopShiftUp = 0 end if not mathparameters.AccentBottomShiftDown then mathparameters.AccentBottomShiftDown = 0 end if not mathparameters.FlattenedAccentTopShiftUp then mathparameters.AccentTopShiftUp = 0 end if not mathparameters.FlattenedAccentBottomShiftDown then mathparameters.AccentBottomShiftDown = 0 end if not mathparameters.AccentBaseDepth then mathparameters.AccentBaseDepth = 0 end if not mathparameters.AccentFlattenedBaseDepth then mathparameters.AccentFlattenedBaseDepth = 0 end if not mathparameters.AccentTopOvershoot then mathparameters.AccentTopOvershoot = 0 end if not mathparameters.AccentBottomOvershoot then mathparameters.AccentBottomOvershoot = 0 end if not mathparameters.AccentSuperscriptDrop then mathparameters.AccentSuperscriptDrop = 0 end if not mathparameters.AccentSuperscriptPercent then mathparameters.AccentSuperscriptPercent = 0 end if not mathparameters.AccentExtendMargin then mathparameters.AccentExtendMargin = 50 end if not mathparameters.DelimiterPercent then mathparameters.DelimiterPercent = 100 end if not mathparameters.DelimiterShortfall then mathparameters.DelimiterShortfall = 0 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", PrimeRaisePercent = "unscaled", PrimeRaiseComposedPercent = "unscaled", PrimeWidthPercent = "unscaled", AccentTopOvershoot = "unscaled", AccentBottomOvershoot = "unscaled", AccentSuperscriptPercent = "unscaled", DelimiterPercent = "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.xheight -- 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 -- -- gone -- 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) if use_math_goodies then 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 if mathematics then local parameters = mathematics.parameters local bigslots = mathematics.bigslots or mathematics.bigs 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) local oldvalue = mathparameters[name] local newvalue = oldvalue if tvalue == "number" then newvalue = value elseif tvalue == "string" then -- delay till all set 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 or 0,newvalue) end mathparameters[name] = newvalue end for name, value in next, parameters do local tvalue = type(value) if tvalue == "string" then local newvalue = mathparameters[value] -- if not newvalue then -- local code = loadstring("return " .. value,"","t",mathparameters) -- if type(code) == "function" then -- local okay, v = pcall(code) -- if okay then -- newvalue = v -- end -- end -- end if newvalue then -- split in number and string mathparameters[name] = newvalue elseif trace_defining then report_math("ignoring math parameter %a: %S",name,value) end end end end if bigslots then target.bigslots = bigslots end end end end end end end -- a couple of predefined tweaks: local mathtweaks = { } mathematics.tweaks = mathtweaks local function report_tweak(fmt,target,original,...) if fmt then local metadata = (original and original.shared.rawdata.metadata) or (target and target .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](...) ) else report_mathtweak("") end end -- { -- tweak = "dimensions", -- list = { -- ["lowercasegreeksansserifbolditalic"] = { -- -- delta = 0x003B1 - 0x1D7AA, -- slant = -0.2, -- line = 0.1, -- mode = 1, -- width = 0.675, -- -- scale = 0.975, -- squeeze = 0.975, -- extend = .7, -- }, -- }, -- }, -- ["0x7C.variants.*"] = { squeeze = 0.10, height = 0.10, depth = 0.10 }, local detail do local splitter = lpeg.tsplitat(".") detail = function(characters,k) if type(k) == "string" then local t = lpegmatch(splitter,k) local n = #t if n > 0 then local base = tonumber(t[1]) or tonumber(t[1],16) if base then local c = characters[base] if c and n > 1 then local list = t[2] if list == "parts" then local nxt = c.next while nxt do c = characters[nxt] nxt = c.next end c = c.hparts or c.vparts if c then local index = t[3] if index == "*" then return t else if index == "top" then index = #c elseif index == "bottom" then index = 1 else index = tonumber(index) end if index then c = c[index] if c then return c.glyph end end end end elseif list == "variants" then local index = t[3] if index == "*" then local t = { } local nxt = c.next while nxt do t[#t+1] = nxt c = characters[nxt] nxt = c.next end return t else index = tonumber(index) if index then local nxt = c.next while nxt and index > 1 do c = characters[nxt] nxt = c.next index = index - 1 end return nxt end end end end end end else return k end end end do local stepper = utilities.parsers.stepper local count = 0 local toeffect = fonts.toeffect local privateslot = fonts.helpers.privateslot local function adapt(list,target,original,targetcharacters,originalcharacters,k,v,compact,n) k = mathgaps[k] or k local character = targetcharacters[k] if character then -- if not character.tweaked then -- todo: add a force local t = type(v) if t == "number" then v = list[v] t = type(v) end if t == "table" and next(v) then local factor = v.factor if factor then local m = v v = setmetatableindex({ width = factor, height = factor, depth = factor, squeeze = factor, extend = factor, }, v) end local original = v.original if not original then local delta = v.delta if delta then original = k + delta end end if original then original = mathgaps[original] or original local data = targetcharacters[original] if data then data = copytable(data) data.unicode = original targetcharacters[k] = data character = data else report_mathtweak("no slot %U",original) return end end -- local width = character.width local height = character.height local depth = character.depth local italic = character.italic local topaccent = character.topaccent -- local widthfactor = v.width local heightfactor = v.height local depthfactor = v.depth local italicfactor = v.italic local anchorfactor = v.anchor local advancefactor = v.advance local xoffsetfactor = v.xoffset local yoffsetfactor = v.yoffset local scalefactor = v.scale local total = (height or 0) + (depth or 0) if scalefactor ~= 1 then character.scale = scalefactor end if width and width ~= 0 then if advancefactor then character.advance = advancefactor * width else character.advance = width -- so advance is oldwidth end if widthfactor then character.width = widthfactor * width end if xoffsetfactor then character.xoffset = xoffsetfactor * width end end if height and height ~= 0 then if heightfactor then character.height = heightfactor * height end end if depth and depthfactor then character.depth = depthfactor * depth end if yoffsetfactor then character.yoffset = yoffsetfactor * total end if italicfactor then if italic then character.italic = italicfactor * italic elseif width and italicfactor ~= 1 then character.italic = italicfactor * width end end if anchorfactor then character.topaccent = anchorfactor * (topaccent or width) end -- begin experiment local line = v.wline if line then local parameters = target.parameters v.line = parameters.hfactor * line / parameters.units end -- end experiment character.effect = toeffect(v) -- todo: move wline test inside here -- begin experiment v.line = line -- end experiment if trace_tweaking then report_tweak("adapting dimensions of %U ",target,original,k) end -- missing when private local originaldata = originalcharacters[k] -- or targetcharacters[k] local smaller = originaldata and originaldata.smaller if compact and smaller and smaller ~= k then adapt(list,target,original,targetcharacters,originalcharacters,smaller,v,compact,n+1) end count = count + 1 else report_mathtweak("invalid dimension entry %U",k) end -- character.tweaked = true if v.all then local nxt = character.next if nxt then adapt(list,target,original,targetcharacters,originalcharacters,nxt,v,compact,n) else local parts = character.hparts if parts then for i=1,#parts do adapt(list,target,original,targetcharacters,originalcharacters,parts[i],v,compact,n) end end end end -- end else report_tweak("no character %U",target,original,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 count = 0 for k, v in sortedhash(list) do local t = type(k) if t == "number" then adapt(list,target,original,targetcharacters,originalcharacters,k,v,compact,1) elseif t == "string" then local d = privateslot(k) or detail(targetcharacters,k) -- watch the private here local t = type(d) if t == "table" then for i=1,#d do adapt(list,target,original,targetcharacters,originalcharacters,d[i],v,compact,1) end elseif t == "number" then adapt(list,target,original,targetcharacters,originalcharacters,d,v,compact,1) elseif d then -- some kind of error else local r = blocks[k] if r then local done = false for i=r.first,r.last do adapt(list,target,original,targetcharacters,originalcharacters,i,v,compact,1) end else stepper(k,function(n) adapt(list,target,original,targetcharacters,originalcharacters,n,v,compact,1) end) end end -- elseif t == "table" then -- for i=1,#t do -- adapt(list,target,original,targetcharacters,originalcharacters,t[i],v,compact,1) -- end end end if trace_tweaking and count > 0 then report_mathtweak("%i dimensions adapted",count) end end end end do function mathtweaks.message(target,original,parameters) report_mathtweak(parameters.text or "no message") end end do function mathtweaks.wipevariants(target,original,parameters) local list = parameters.list if list then local targetcharacters = target.characters -- local originalcharacters = original.characters local count = 0 for k, v in sortedhash(list) do local ori = targetcharacters[k] local nxt = ori.next local cnt = v if nxt then local hpt, vpt local lst = { } while nxt do local chr = targetcharacters[nxt] lst[#lst+1] = chr nxt = chr.next if not nxt then hpt = chr.hparts vpt = chr.vparts break end end if hpt or vpt then count = count + 1 if cnt ~= "*" then if #lst < cnt then cnt = #lst end ori = lst[cnt] end ori.hparts = hpt ori.vparts = vpt -- ori.next = nil -- so we keep the chain end end end if trace_tweaking and count > 0 then report_mathtweak("%i variants wiped",count) end end end end do function mathtweaks.replacements(target,original,parameters) local list = parameters.list if list then local targetcharacters = target.characters local originalcharacters = original.characters local unicodes = original.resources.unicodes if unicodes then local count = 0 for k, v in sortedhash(list) do if type(v) == "string" then v = unicodes[v] end if type(v) == "number" then targetcharacters[mathgaps[k] or k] = targetcharacters[mathgaps[v] or v] count = count + 1 end end if trace_tweaking and count > 0 then report_mathtweak("%i permanent replacements",count) end end end end function mathtweaks.substitutes(target,original,parameters) local list = parameters.list if list then local targetcharacters = target.characters local originalcharacters = original.characters local getsubstitution = fonts.handlers.otf.getsubstitution local count = 0 for k, v in next, list do -- no need for sortedhash(list) unless we report local sub = getsubstitution(original,k,v,true) if sub then targetcharacters[mathgaps[k] or k] = targetcharacters[mathgaps[sub] or sub] count = count + 1 end end if trace_tweaking and count > 0 then report_mathtweak("%i permanent substitutions",count) end end end mathtweaks.replace = mathtweaks.replacements mathtweaks.substitute = mathtweaks.substitutes end do -- maybe we'll have a different name mathtweaks.subsets = { acenorsuvxz = { 0x1D44E, 0x1D450, 0x1D452, 0x1D45B, 0x1D45C, 0x1D45F, 0x1D460, 0x1D462, 0x1D463, 0x1D465, 0x1D467 }, bhklt = { 0x1D44F, 0x1D455, 0x1D458, 0x1D459, 0x1D461 }, d = { 0x1D451 }, f = { 0x1D453 }, gjqy = { 0x1D454, 0x1D457, 0x1D45E, 0x1D466 }, i = { 0x1D456 }, mw = { 0x1D45A, 0x1D464}, p = { 0x1D45D }, } function mathtweaks.kernpairs(target,original,parameters) local list = parameters.list if list then local targetcharacters = target.characters local originalcharacters = original.characters local count = 0 local function add(v,n) local chardata = targetcharacters[mathgaps[n] or n] if chardata then local width = chardata.width if width then local kerns = chardata.kerns or { } for kk, vv in next, v do -- for kk, vv in sortedhash(v) do stepper(kk,function(nn) -- todo: also make stepper accept a table local t = mathgaps[nn] or nn if t then kerns[t] = vv * width count = count + 1 end end) end chardata.kerns = kerns end end end for k, v in next, list do -- no need for sortedhash(list) unless we report -- for k, v in sortedhash(list) do -- no need for sortedhash(list) unless we report stepper(k,function(n) -- todo: also make stepper accept a table add(v,n) end) end -- for k, v in next, list do -- no need for sortedhash(list) unless we report -- local chardata = targetcharacters[mathgaps[k] or k] -- if chardata then -- local width = chardata.width -- if width then -- local kerns = chardata.kerns or { } -- for kk, vv in next, v do -- local t = mathgaps[kk] or kk -- if t then -- kerns[t] = vv * width -- count = count + 1 -- end -- end -- chardata.kerns = kerns -- end -- end -- end if trace_tweaking and count > 0 then report_mathtweak("%i kern pairs",count) end end end end do -- What a mess ... should be a different alphabet. local function expand(target,original,list,selector,feature) if feature then local getsubstitution = fonts.handlers.otf.getsubstitution for _, char in next, list do local variant = getsubstitution(original,char,feature,true) if variant then mathematics.addvariant(target,char,variant,selector) end end else for i, char in next, list do mathematics.addvariant(target,char,0xFE800+i,selector) end end end function mathtweaks.variants(target,original,parameters) local feature = parameters.feature local selector = parameters.selector local kind = parameters.kind if kind == "script" then if selector then expand(target,original,mathematics.alphabets.sr.tf.lcletters,selector,feature) expand(target,original,mathematics.alphabets.sr.tf.ucletters,selector,feature) end end end mathtweaks.variant = mathtweaks.variants end do -- see changed hack in math-fbk local nps = fonts.helpers.newprivateslot local list = { { 0x2032, { nps("prime 0x2032 1"), nps("prime 0x2032 2"), nps("prime 0x2032 3") }, 1 }, { 0x2033, { nps("prime 0x2033 1"), nps("prime 0x2033 2"), nps("prime 0x2033 3") }, 2 }, { 0x2034, { nps("prime 0x2034 1"), nps("prime 0x2034 2"), nps("prime 0x2034 3") }, 3 }, { 0x2057, { nps("prime 0x2057 1"), nps("prime 0x2057 2"), nps("prime 0x2057 3") }, 4 }, { 0x2035, { nps("prime 0x2035 1"), nps("prime 0x2035 2"), nps("prime 0x2035 3") }, 1 }, { 0x2036, { nps("prime 0x2036 1"), nps("prime 0x2036 2"), nps("prime 0x2036 3") }, 2 }, { 0x2037, { nps("prime 0x2037 1"), nps("prime 0x2037 2"), nps("prime 0x2037 3") }, 3 }, } local function copytable(t) return { width = t.width, height = t.height, depth = t.depth, index = t.index, unicode = t.unicode, } end local lastprivate local function use(target,original,targetcharacters,charcode,private,newheight,scale,fake,keep,count,smaller) if count == 1 then lastprivate = private end if keep then if trace_tweaking then report_tweak("keeping prime %U",target,original,charcode) end return end if fake and count > 1 then local olddata = targetcharacters[lastprivate] if olddata then -- todo: when keep local oldheight = scale * olddata.height local oldwidth = scale * olddata.width local yoffset = (newheight - oldheight) / scale local xoffset = fake * oldwidth local newwidth = oldwidth + (count - 1) * xoffset targetcharacters[charcode] = { yoffset = yoffset, width = newwidth, height = newheight, smaller = smaller, unicode = olddata.unicode, commands = { { "offset", 0, 0, lastprivate, scale, scale }, { "offset", xoffset, 0, lastprivate, scale, scale }, count > 2 and { "offset", 2 * xoffset, 0, lastprivate, scale, scale } or nil, count > 3 and { "offset", 3 * xoffset, 0, lastprivate, scale, scale } or nil, }, } if trace_tweaking then report_tweak("faking %U with %i primes",target,original,charcode,count) end return end end local olddata = targetcharacters[private] if olddata then local oldheight = scale * olddata.height local oldwidth = scale * olddata.width local yoffset = (newheight - oldheight) / scale targetcharacters[charcode] = { yoffset = yoffset, width = oldwidth, height = newheight, smaller = smaller, unicode = olddata.unicode, commands = { { "offset", 0, 0, private, scale, scale } }, } if trace_tweaking then report_tweak("fixing prime %U",target,original,charcode) end return end if trace_tweaking then report_tweak("unable to fix prime %U",target,original,charcode) end end function mathtweaks.fixprimes(target,original,parameters) local targetcharacters = target.characters local targetparameters = target.parameters local originalcharacters = original.characters local factor = parameters.factor or 0.85 local scale = parameters.scale or 1 local smaller = parameters.smaller local fake = parameters.fake local keep = parameters.keep and targetparameters.mathsize == 1 local newheight = factor * target.mathparameters.AccentBaseHeight local compact = targetparameters.textscale and true or false -- lastprivate = false -- make virtual copies (just all of them, also for tracing) for i=1,#list do local entry = list[i] local count = entry[3] if not fake or count == 1 then local c1 = entry[1] local d1 = targetcharacters[c1] if d1 then local pc = entry[2] local c2 = d1.smaller or c1 local d2 = targetcharacters[c2] local c3 = d2.smaller or c2 local d3 = targetcharacters[c3] if smaller then d1 = d2 d2 = d3 end targetcharacters[pc[1]] = copytable(d1) targetcharacters[pc[2]] = copytable(d2) targetcharacters[pc[3]] = copytable(d3) end end end -- replace for i=1,#list do local entry = list[i] local count = entry[3] local c1 = entry[1] local pc = entry[2] local s1 = pc[1] local d1 = targetcharacters[c1] if compact and d1 then local c2 = d1.smaller or c1 local d2 = targetcharacters[c2] local c3 = d2.smaller or c2 local s2 = pc[2] local s3 = pc[3] use(target,original,targetcharacters,c1,s1,newheight,scale,fake,keep, count,c2) use(target,original,targetcharacters,c2,s2,newheight,scale,fake,false,count,c3) use(target,original,targetcharacters,c3,s3,newheight,scale,fake,false,count) else use(target,original,targetcharacters,c1,s1,newheight,scale,fake,keep,count) end end end end do local nps = fonts.helpers.newprivateslot local privates = { [0x2212] = nps("unary minus"), [0x002B] = nps("unary plus"), [0x00B1] = nps("unary plus minus"), [0x2213] = nps("unary minus plus"), } -- these are the values tested with texgyre-bonum local predefined = { ["unary minus"] = { original = 0x2212, extend = .5, width = .5, unicode = 0x002D, -- hyphen minus }, ["unary plus"] = { original = 0x002B, extend = .5, squeeze = .5, width = .5, height = .5, yoffset = .2, mode = 2, wline = .5, unicode = 0x002B, }, ["unary plus minus"] = { original = 0x00B1, extend = .5, squeeze = .5, width = .5, height = .5, yoffset = .2, mode = 2, wline = .5, }, ["unary minus plus"] = { original = 0x2213, extend = .5, squeeze = .5, width = .5, height = .5, yoffset = .2, mode = 2, wline = .5, }, } -- { -- tweak = "addprivates", -- list = { -- -- for specific parameters see act file -- ["unary minus"] = { preset = "unary minus" }, -- ["unary plus"] = { preset = "unary plus" }, -- ["unary plus minus"] = { preset = "unary plus minus" }, -- ["unary minus plus"] = { preset = "unary minus plus" }, -- }, -- }, function mathtweaks.addprivates(target,original,parameters) local list = parameters.list or predefined if list then local targetcharacters = target.characters local targetparameters = target.parameters local originalcharacters = original.characters local processedprivates = { } for name, v in sortedhash(list) do if type(v) == "table" then local preset = v.preset if preset then local p = predefined[preset] if p then v = table.combine(p,v) p.preset = nil else goto NEXT end end local charslot = v.original if charslot then local chardata = targetcharacters[charslot] if chardata then local clonedata = copytable(chardata) local cloneslot = nps(name) local unicode = v.unicode or clonedata.unicode clonedata.uncode = unicode targetcharacters[cloneslot] = clonedata if trace_tweaking then report_tweak("cloning %a from %C into %U with tounicode %U",target,original,name,charslot,cloneslot,unicode) end end processedprivates[name] = v end ::NEXT:: end end mathtweaks.dimensions(target,original,{ tweak = parameters.tweak, list = processedprivates, }) end end end do function mathtweaks.fixanchors(target,original,parameters) local targetcharacters= target.characters local factor = tonumber(parameters.factor) or 0 if factor ~= 0 then for k, v in next, targetcharacters do local a = v.topaccent if a and a > 0 then v.topaccent = a * factor end end end end -- local default = { -- "digitsnormal", -- "lowercasedoublestruck", -- "uppercasedoublestruck", -- } local function wipe(target,original,parameters,field) local targetcharacters = target.characters local function step(s,l) local done = false while s do local c = targetcharacters[mathgaps[s] or s] if c then local v = c[field] if v then if trace_tweaking then if l then report_tweak("removing %a in range %a from %C",target,original,field,l,s) else report_tweak("removing %a from %C",target,original,field,s) end end c[field] = nil done = true end s = c.smaller else break end end return done end local list = parameters.list -- todo: ranges -- if list == "default" then -- list = default -- else if type(list) == "string" then list = { list } end for i=1,#list do local l = list[i] local r = blocks[l] if r then local done = false for i=r.first,r.last do if step(i,l) then done = true end end if not done and trace_tweaking then report_mathtweak("there is no need to remove %a range %a",field,l) end else stepper(l,step) end end end function mathtweaks.wipeanchors(target,original,parameters) wipe(target,original,parameters,"topaccent") end function mathtweaks.wipeitalics(target,original,parameters) wipe(target,original,parameters,"italic") end -- function mathtweaks.fixdigits(target,original,parameters) -- mathtweaks.fixanchors(target,original,{ list = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 } }) -- end end do -- musical timestamp: March 2022, Antonio Sanches (Bad Hombre), live performance in NL function mathtweaks.kerns(target,original,parameters) local kerns = parameters.list if kerns then local characters = target.characters local function setone(uc,data) local function set(unicode) unicode = mathgaps[unicode] or unicode local chardata = characters[unicode] if chardata then local width = chardata.width or 0 local k = data.topleft ; if k and k ~= 0 then chardata.topleft = k * width end local k = data.topright ; if k and k ~= 0 then chardata.topright = k * width end local k = data.bottomleft ; if k and k ~= 0 then chardata.bottomleft = k * width end local k = data.bottomright ; if k and k ~= 0 then chardata.bottomright = k * width end end end local unicode = detail(characters,uc) if type(unicode) == "table" then for i=1,#unicode do set(unicode[i]) end elseif unicode then set(unicode) end end for unicode, data in next, kerns do setone(unicode,data) -- withscriptcode(tfmdata,unicode,data,kernone) -- also smaller end end end end do function mathtweaks.margins(target,original,parameters) local margins = parameters.list if margins then local characters = target.characters local function setone(unicode,data) local chardata = characters[mathgaps[unicode] or unicode] local width = chardata.width or 0 local total = (chardata.height or 0) + (chardata.depth or 0) local k = data.left ; if k and k ~= 0 then chardata.leftmargin = k * width end local k = data.right ; if k and k ~= 0 then chardata.rightmargin = k * width end local k = data.top ; if k and k ~= 0 then chardata.topmargin = k * total end local k = data.bottom ; if k and k ~= 0 then chardata.bottommargin = k * total end end for unicode, data in next, margins do setone(unicode,data) -- withscriptcode(tfmdata,unicode,data,kernone) -- also smaller end end end end do -- musical timestamp: June 2022, Porcupine Tree - Rats Return -- we can actually share these and flag them as being tweaked local function scale(t,width,total) local r = { } for i=1,#t do local ti = t[i] local kern = ti.kern local height = ti.height if kern then kern = width * kern end if height then height = total * height end r[i] = { kern = kern or 0, height = height or 0, } end return r end function mathtweaks.staircase(target,original,parameters) local kerns = parameters.list if kerns then local characters = target.characters local function kernone(unicode,data) local chardata = characters[mathgaps[unicode] or unicode] local total = (chardata.height or 0) + (chardata.depth or 0) local width = chardata.width or 0 if data then local tl = data.topleft ; if tl then tl = scale(tl,width,total) end local tr = data.topright ; if tr then tr = scale(tr,width,total) end local bl = data.bottomleft ; if bl then bl = scale(bl,width,total) end local br = data.bottomright ; if br then br = scale(br,width,total) end chardata.mathkerns = { topleft = tl, ropright = tr, bottomleft = bl, bottomright = br, } else chardata.mathkerns = nil end end for unicode, data in next, kerns do kernone(unicode,data) -- withscriptcode(tfmdata,unicode,data,kernone) -- also smaller end end end end do -- local list = { -- [0x203E] = { factor = .4 }, -- overbar -- [0x203E] = { factor = .7 }, -- underbar -- [0x23DE] = { factor = .4 }, -- overbrace -- [0x23DF] = { factor = .7 }, -- underbrace -- [0x23DC] = { factor = .4 }, -- overparent -- [0x23DD] = { factor = .7 }, -- underparent -- [0x23B4] = { factor = .4 }, -- overbracket -- [0x23B5] = { factor = .7 }, -- underbracket -- } -- We can patch the dimensions in-place or we can use additional characters in -- the private namespace. -- local addprivate = fonts.helpers.addprivate -- local newnextglyph = addprivate(target,formatters["M-N-%H"](nextglyph),newnextdata) local over = { factor = "over" } local under = { factor = "under" } local candidates = { over = { [0x203E] = over, -- overbar [0x23DE] = over, -- overbrace [0x23DC] = over, -- overparent [0x23B4] = over, -- overbracket }, under = { [0x23DF] = under, -- underbrace [0x23DD] = under, -- underparent [0x23B5] = under, -- underbracket }, accent = { [0x0300] = over, -- widegrave [0x0308] = over, -- wideddot [0x0304] = over, -- widebar [0x0301] = over, -- wideacute [0x0302] = over, -- widehat [0x030C] = over, -- widecheck [0x0306] = over, -- widebreve [0x0307] = over, -- widedot [0x030A] = over, -- widering [0x0303] = over, -- widetilde [0x20DB] = over, -- widedddot }, } local function adapt(c,factor,baseheight,basedepth) -- if not c.tweaked then local height = c.height or 0 local depth = c.depth or 0 local yoffset = 0 if factor == "over" then local h = height - baseheight yoffset = h - height height = h depth = depth - baseheight elseif factor == "under" then local d = depth - basedepth yoffset = depth - d depth = d height = height - baseheight elseif height > 0 then local h = tonumber(factor) * height yoffset = h - height height = h elseif depth > 0 then local d = tonumber(factor) * depth yoffset = depth - d depth = d end c.yoffset = yoffset ~= 0 and yoffset or nil c.height = height > 0 and height or nil c.depth = depth > 0 and depth or nil -- c.tweaked = true -- end end local function process(target,original,characters,list,baseheight,basedepth) if list then for k, v in sortedhash(list) do -- sort for tracing local c = characters[k] if c and not c.yoffset then local factor = v.factor if factor then adapt(c,factor,baseheight,basedepth) local nc = c.next local nv = 0 local ns = 0 while nc do local c = characters[nc] if c then adapt(c,factor,baseheight,basedepth) nv = nv + 1 nc = c.next if not nc then local hv = c.hparts if hv then for i=1,#hv do local c = characters[hv[i].glyph] if c then adapt(c,factor,baseheight,basedepth) ns = ns + 1 end end end break end else break end end if trace_tweaking then report_tweak("adapting extensible (%i sizes, %i parts) %U",target,original,k,nv,ns) end end end end end end function mathtweaks.accentdimensions(target,original,parameters) local list = parameters.list or { "over", "under" } if list then local characters = target.characters local baseheight = target.mathparameters.AccentBaseHeight or 0 local basedepth = target.mathparameters.AccentBaseDepth or 0 for k, v in sortedhash(list) do -- sort for tracing local t = type(v) if t == "string" then v = candidates[v] t = type(v) end if t == "table" then process(target,original,characters,v,baseheight,basedepth) end end end end end do local addprivate = fonts.helpers.addprivate local privateslot = fonts.helpers.privateslot -- function mathtweaks.addrules(target,original,parameters) -- local characters = target.characters -- local height = target.mathparameters.OverbarRuleThickness -- local depth = target.mathparameters.UnderbarRuleThickness -- local width = target.parameters.emwidth/2 -- local step = 0.8 * width -- characters[0x203E] = { -- over -- width = width, -- height = height, -- depth = 0, -- unicode = 0x203E, -- commands = { { "rule", height, width } }, -- hparts = { -- { advance = width, ["end"] = step, glyph = 0x203E, start = 0 }, -- { advance = width, ["end"] = 0, glyph = 0x203E, start = step, extender = 1 }, -- } -- } -- characters[0x0332] = { -- under -- width = width, -- height = 0, -- depth = depth, -- yoffset = -depth, -- unicode = 0x0332, -- commands = { { "rule", height, width } }, -- hparts = { -- { advance = width, ["end"] = step, glyph = 0x0332, start = 0 }, -- { advance = width, ["end"] = 0, glyph = 0x0332, start = step, extender = 1 }, -- } -- } -- end function mathtweaks.addrules(target,original,parameters) local characters = target.characters local thickness = target.mathparameters.OverbarRuleThickness local width = target.parameters.emwidth / 2 local step = 0.8 * width characters[0x203E] = { -- over width = width, height = thickness / 2, depth = thickness / 2, yoffset = - thickness / 2, unicode = 0x203E, commands = { { "rule", thickness, width } }, hparts = { { advance = width, ["end"] = step, glyph = 0x203E, start = 0 }, { advance = width, ["end"] = 0, glyph = 0x203E, start = step, extender = 1 }, } } -- characters[0x0332] = characters[0x203E] -- -- lucida lacks them ... -- local half = thickness / 2 local double = thickness * 2 -- if not characters[0x23B4] then local tpiece = addprivate(target,"bracket-piece-top",{ width = thickness, height = half, depth = double, yoffset = - double, commands = { { "rule", thickness * 2.5, thickness } }, }) characters[0x23B4] = { -- over width = width, height = half, depth = double, unicode = 0x23B4, commands = { { "rule", thickness, width } }, hparts = { { advance = thickness, glyph = tpiece, ["end"] = 0, start = half }, { advance = width, glyph = 0x203E, ["end"] = step, start = step, extender = 1 }, { advance = thickness, glyph = tpiece, ["end"] = half, start = 0 }, } } end if not characters[0x23B5] then local bpiece = addprivate(target,"bracket-piece-bottom",{ width = thickness, height = double, depth = half, yoffset = - half, commands = { { "rule", thickness * 2.5, thickness } }, }) characters[0x23B5] = { -- over width = width, height = double, depth = half, unicode = 0x23B5, commands = { { "rule", thickness, width } }, hparts = { { advance = thickness, glyph = bpiece, ["end"] = 0, start = half }, { advance = width, glyph = 0x203E, ["end"] = step, start = step, extender = 1 }, { advance = thickness, glyph = bpiece, ["end"] = half, start = 0 }, } } end -- end local force = false experiments.register("math.arrows", function(v) force = v end) local function tighten(target,unicode,left,right,squeeze,yoffset) local name = string.formatters["math tightened %U %.3N %.3N %.3N %.3N"](unicode,left,right,squeeze,yoffset) local slot = privateslot(target,name) if not slot then local characters = target.characters local data = copytable(characters[unicode]) local width = data.width data.advance = width data.width = width * (1-left-right) data.xoffset = width * -left if squeeze ~= 1 then data.effect = { squeeze = squeeze } end if yoffset ~= 0 then data.yoffset = (data.height or 0) * yoffset end slot = addprivate(target,name,data) end return slot end local function create(target,unicode,list,overloads) local characters = target.characters local chardata = characters[unicode] if chardata then local endpoint = unicode while chardata.next do chardata = characters[chardata.next] end -- if chardata and (force or not chardata.hparts) then if chardata and (force or overloads[unicode] == false or not chardata.hparts) then if not list then chardata.hparts = nil -- when we test else local overload = overloads[unicode] local hparts = { } for i=1,#list do local part = list[i] local glyph = part.glyph or unicode local check = overloads[glyph] local left = (check and check.left ) or part.left or 0 local right = (check and check.right ) or part.right or 0 local squeeze = check and check.squeeze or 1 local yoffset = check and check.yoffset or 0 if left~= 0 or right ~= 0 or squeeze ~= 1 or yoffset ~= 0 then glyph = tighten(target,glyph,left,right,squeeze,yoffset) end local width = characters[glyph].width local step = width/2 if part.extensible then hparts[#hparts+1] = { advance = width, glyph = glyph, ["end"] = step, start = step, extender = 1, } else hparts[#hparts+1] = { advance = width, glyph = glyph, ["end"] = 0, start = step, } end end if #hparts == #list then chardata.hparts = hparts end end end end end -- Unicode math lacks the arrow snippet while it does have fence snippets. Also, some -- fonts have a relbar that doesn't match the double arrow. -- -- { -- tweak = "addarrows", -- list = { [0x3D] = { squeeze = .85, yoffset = .0975 } } -- }, -- -- We have no begin and end snippet, so I played with centering and rules at the edges -- -- [0x21A9] = { -- hookleftarrow -- { glyph = 0x2212, left = slack, extensible = true }, -- { glyph = 0x21A9, right = slack }, -- { glyph = 0x2212, right = slack, extensible = true }, -- } -- -- but in the end rejected it. local function initialize(left, right, slack) -- We save some space with locals. When no glyph is given the unicode itself is -- used which also saves some. local single = { glyph = 0x2212, left = slack, right = slack, extensible = true } local double = { glyph = 0x003D, left = slack, right = slack, extensible = true } local triple = { glyph = 0x2261, left = slack, right = slack, extensible = true } ----- spacer = { glyph = 0x0020, left = slack, right = slack, extensible = true } local slackslack = { left = slack, right = slack } local leftslack = { left = left, right = slack } local slackright = { left = slack, right = right } ----- centered = { spacer, { }, spacer } local centered = false -- the luametatex engine does this local singleright = { single, slackright } local leftsingle = { leftslack, single } return { -- [0x002D] = { { left = slack, right = slack, glyph = 0x2212 }, single }, -- rel -- [0x2190] = leftsingle, -- leftarrow [0x219E] = leftsingle, -- twoheadleftarrow [0x21BC] = leftsingle, -- leftharpoonup [0x21BD] = leftsingle, -- leftharpoondown -- [0x2192] = singleright, -- rightarrow [0x21A0] = singleright, -- twoheadrightarrow [0x21C0] = singleright, -- rightharpoonup [0x21C1] = singleright, -- rightharpoondown -- [0x003D] = { slackslack, double }, -- equaltext [0x2261] = { slackslack, triple }, -- triplerel [0x27F8] = { leftslack, double }, -- Leftarrow [0x27F9] = { double, slackright }, -- Rightarrow -- [0x21A9] = centered, -- hookleftarrow [0x21AA] = centered, -- hookrightarrow [0x21CB] = centered, -- leftrightharpoons [0x21CC] = centered, -- rightleftharpoons [0x21C4] = centered, -- rightoverleftarrow [0x21A6] = centered, -- mapsto -- [0x203E] = { slackslack, { left = slack, right = slack, extensible = true } }, -- bar -- [0x27F7] = { { glyph = 0x2190, left = left, right = slack }, single, { glyph = 0x2192, left = slack, right = right } }, -- leftrightarrow rightleftarrow [0x27FA] = { { glyph = 0x27F8, left = left, right = slack }, double, { glyph = 0x27F9, left = slack, right = right } }, -- Leftrightarrow Rightleftarrow } end function mathtweaks.addarrows(target,original,parameters) local overloads = parameters.list or { } -- { [unicode] = { left = .1, right = .1 } } local left = parameters.left or 0.05 local right = parameters.right or 0.05 local slack = parameters.slack or 0.1 local arrows = initialize(left,right,slack) -- inspect(arrows) for unicode, list in sortedhash(arrows) do create(target,unicode,list,overloads) end end end do -- this could be combined with the previous function mathtweaks.addparts(target,original,parameters) local characters = target.characters local list = parameters.list if list then for unicode, data in sortedhash(list) do local template = data.template if template then local source = characters[template] local target = characters[unicode] if source and target then local sequence = data.sequence local horizontal = data.horizontal if sequence then local parts = horizontal and source.hparts or source.vparts if parts then local p = { } for i=1,#sequence do local step = sequence[i] local glyph = step.glyph if glyph == "first" or glyph == "last" then local g = glyph == "first" and 1 or #parts local c = fastcopy(parts[g]) local f = step.factor if f then c["end"] = f * (c["end"] or 0) c.start = f * (c.start or 0) end p[#p+1] = c else local c = characters[glyph] if c then p[#p+1] = { glyph = glyph, advance = c.width, start = 0, ["end"] = 0, } end end end if #p > 0 then target[horizontal and "hparts" or "vparts"] = p end end end end end 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 local height = 0 -- local depth = 0 if method == "c" then local template = characters[fraction] width = template.width height = template.height -- depth = template.depth elseif method == "s" then width = fraction * parameters.space -- space height = 0 -- depth = 0 else width = fraction * parameters.quad -- quad height = 0 -- depth = 0 end if trace_tweaking then report_tweak("setting width of %U to %p",target,original,unicode,width) end characters[unicode] = { width = width, -- advance = width, height = height, -- depth = depth, unicode = unicode, commands = { -- { "slot", 0, 32 }, }, } 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 local list = { 0x2061, 0x2062, 0x2063, 0x2064 } function mathtweaks.wipecues(target,original,parameters) local characters = target.characters local tobewiped = parameters.list or list for i=1,#tobewiped do local unicode = tobewiped[i] characters[unicode] = { width = 0, height = 0, depth = 0, unicode = unicode, } if trace_tweaking then report_tweak("character %U has been wiped",target,original,unicode) end end end end do local mapping = { [0x002F] = 0x2044, } function mathtweaks.fixslashes(target,original,parameters) local characters = target.characters for normal, weird in sortedhash(mapping) do local normalone = characters[normal] local weirdone = characters[weird] if normalone and weirdone and not normalone.next then normalone.next = weirdone.next if trace_tweaking then report_tweak("extensibles from %U used for %U",target,original,weird,normal) end end weirdone = copytable(normalone) characters[weird] = weirdone weirdone.unicode = weird end end end do -- see pagella for an extensive example local mapping = { [0x0300] = { 0x0060, false }, -- aliases can be a table [0x0308] = { 0x00A8, false }, [0x0304] = { 0x00AF, false }, [0x0301] = { 0x00B4, false }, [0x0302] = { 0x02C6, true }, [0x030C] = { 0x02C7, true }, [0x0306] = { 0x02D8, false }, [0x0307] = { 0x02D9, false }, [0x030A] = { 0x02DA, false }, [0x0303] = { 0x02DC, true }, [0x20DB] = { 0x20DB, false }, } local hat = fonts.helpers.newprivateslot("hat 0x0302") -- todo other sizes function mathtweaks.fixaccents(target,original,parameters) local characters = target.characters characters[hat] = copytable(characters[0x0302]) -- TODO for stretching, entry in sortedhash(mapping) do local alias = entry[1] local stretchingdata = characters[stretching] if stretchingdata and stretchingdata.width == 0 then local topaccent = stretchingdata.topaccent or 0 local width = -topaccent topaccent = width/2 stretchingdata.width = width stretchingdata.topaccent = topaccent stretchingdata.commands = { rightcommand[width + topaccent], charcommand[stretching] } if trace_tweaking then report_tweak("width of initial extensible accent %U set",target,original,stretching) end end end end function mathtweaks.extendaccents(target,original,parameters) local characters = target.characters for stretching, entry in sortedhash(mapping) do local extend = entry[2] local stretchingdata = characters[stretching] if extend then local last = stretchingdata while last do local n = last.next if n then last = characters[n] else last.extensible = true local flataccent = last.flataccent if flataccent then characters[flataccent].extensible = true end break end end end end end function mathtweaks.copyaccents(target,original,parameters) local characters = target.characters for stretching, entry in sortedhash(mapping) do local alias = entry[1] if alias ~= stretching then local stretchingdata = characters[stretching] if stretchingdata then -- we need to nil [x|y]offsets characters[alias] = { width = stretchingdata.width, height = stretchingdata.height, depth = stretchingdata.depth, next = stretchingdata.next, commands = { charcommand[stretching] }, topaccent = stretchingdata.topaccent, -- unicode = stretching, -- when we aliasize to combiners unicode = alias, -- when we keep the original } if trace_tweaking then report_tweak("extensibles accent %U copied to %U",target,original,stretching,alias) end end end end end end do local single = 0x003D local double = 0x2A75 local triple = 0x2A76 function mathtweaks.addequals(target,original,parameters) local characters = target.characters local basechar = characters[single] local width = basechar.width local height = basechar.height local depth = basechar.depth local advance = (parameters.advance or 1/20) * width local char = charcommand[single] local left = leftcommand[advance] characters[double] = { unicode = double, width = 2*width - 1*advance, height = height, depth = depth, callback = "devirtualize", commands = { char, left, char }, } characters[triple] = { unicode = triple, width = 3*width - 2*advance, height = height, depth = depth, callback = "devirtualize", commands = { char, left, char, left, char }, } if trace_tweaking then report_tweak("double %U and triple %U equals added",target,original,double,triple) end end end do -- If we really want, we can have variants that also match radicals but in practice -- radicals and actuarians are never seen together. We could also have a smaller -- extender. local nps = fonts.helpers.newprivateslot local radical = 0x0221A local actuarian = nps("delimited right annuity") -- 0x020E7 local nairautca = nps("delimited left annuity" ) local placehold = nps("delimited ghost annuity") function mathtweaks.addactuarian(target,original,parameters) local characters = target.characters local parameters = target.parameters local linewidth = target.MathConstants.RadicalRuleThickness -- make option local basechar = characters[radical] local baseheight = basechar.height local basedepth = basechar.depth local basetotal = baseheight + basedepth local used = baseheight -- characters[0x020E7] = { width = 6*linewidth, height = baseheight, depth = basedepth, unicode = actuarian, callback = "devirtualize", commands = { upcommand[baseheight-4*linewidth], { "rule", linewidth, 4*linewidth }, downcommand[basetotal/2-linewidth], { "rule", basetotal/2, linewidth }, }, } -- characters[actuarian] = { width = 2*linewidth, height = baseheight, depth = basedepth, unicode = actuarian, callback = "devirtualize", commands = { downcommand[basedepth], { "rule", basetotal, linewidth }, }, vparts = { { advance = basetotal, ["end"] = used, glyph = actuarian, start = 0, }, { advance = basetotal, ["end"] = 0, extender = 1, glyph = actuarian, start = used, }, } } characters[placehold] = { width = 2*linewidth, height = baseheight, depth = basedepth, unicode = actuarian, -- whatever callback = "devirtualize", commands = { rightcommand[linewidth], downcommand[basedepth], { "rule", basetotal, 0 }, }, } characters[nairautca] = { width = 2*linewidth, height = baseheight, depth = basedepth, unicode = actuarian, -- whatever callback = "devirtualize", -- commands = { -- rightcommand[linewidth], -- downcommand[basedepth], -- { "rule", basetotal, linewidth }, -- }, commands = { rightcommand[linewidth], upcommand[baseheight-4*linewidth], { "rule", 4*linewidth, linewidth }, }, vparts = { { advance = basetotal, ["end"] = used, extender = 1, glyph = placehold, start = 0, }, { advance = basetotal, ["end"] = 0, glyph = nairautca, start = used, }, } } if trace_tweaking then report_tweak("actuarian %U added",target,original,actuarian) end end end do -- todo: make callback because we can delay it but then we need to stack -- callbacks local nps = fonts.helpers.newprivateslot local list = { { 0x302, nps("delimited right hat" ), nps("delimited ghost hat" ) }, { 0x303, nps("delimited right tilde"), nps("delimited ghost tilde") }, { 0x30C, nps("delimited right check"), nps("delimited ghost check") }, } function mathtweaks.addfourier(target,original,parameters) local characters = target.characters for i=1,#list do local entry = list[i] local basecode = entry[1] local fouriercode = entry[2] local movecode = entry[3] local basechar = characters[basecode] local scale = parameters.scale or 1 local variant = parameters.variant if variant then for i=1,variant do local okay = basechar.next if okay then basecode = okay basechar = characters[basecode] else break end end end local baseheight = scale * (basechar.height or 0) local basedepth = scale * (basechar.depth or 0) local basewidth = scale * (basechar.width or 0) local used = baseheight/2 local total = baseheight + basedepth characters[movecode] = { width = basewidth, height = used, unicode = basecode, -- callback = "devirtualize", commands = { downcommand[used], { "rule", used, 0 }, }, } characters[fouriercode] = { width = basewidth, height = baseheight, depth = basedepth, unicode = basecode, -- callback = "devirtualize", commands = { scale == 1 and charcommand[basecode] or { "slot", 0, basecode, scale, scale }, }, vparts = { { advance = used, ["end"] = used, extender = 1, glyph = movecode, start = used, }, { advance = total, ["end"] = 0, glyph = fouriercode, start = total, }, } } if trace_tweaking then report_tweak("fourier %U added using %U",target,original,basecode,fourier) end end end end do -- \im{\left\Uchar"007C \frac{1}{2} \right\Uchar"007C} -- \im{\left\Uchar"2016 \frac{1}{2} \right\Uchar"2016} -- \im{\left\Uchar"2980 \frac{1}{2} \right\Uchar"2980} local single = 0x007C local double = 0x2016 local triple = 0x2980 local function extensible(unicode,total,used) return { { advance = total, ["end"] = used, glyph = unicode, start = 0, -- start = used/5, }, { advance = total, -- ["end"] = 0, ["end"] = used/5, -- prevents small gap with inward curved endpoints extender = 1, glyph = unicode, start = used, }, } end function mathtweaks.addbars(target,original,parameters) local characters = target.characters local basechar = characters[single] local width = basechar.width local height = basechar.height local depth = basechar.depth local advance = (parameters.advance or 1/10) * width -- local used = 0.8*height local used = 1.2*height -- large overlap because no smaller pieces local total = height + depth characters[single].vparts = extensible(single,total,used) characters[double] = { unicode = double, width = 2*width - 1*advance, height = height, depth = depth, vparts = extensible(double,total,used), callback = "devirtualize", commands = { charcommand[single], leftcommand[advance], charcommand[single], }, } characters[triple] = { unicode = triple, width = 3*width - 2*advance, height = height, depth = depth, vparts = extensible(triple,total,used), callback = "devirtualize", commands = { charcommand[single], leftcommand[advance], charcommand[single], leftcommand[advance], charcommand[single], }, } if trace_tweaking then report_tweak("triple bars %U added",target,original,triple) end end end do -- lucida: \im{\cdot \ldot \cdots \ldots} local snormal = 0x002E local sraised = 0x22C5 local tnormal = 0x2026 local traised = 0x22EF function mathtweaks.fixellipses(target,original,parameters) local characters = target.characters local function fix(normal,raised) local normalone = characters[normal] if normalone then local raisedone = copytable(normalone) characters[raised] = raisedone raisedone.unicode = weird local height = raisedone.height local yoffset = (parameters.yoffset or 2) * height raisedone.yoffset = yoffset raisedone.height = height + yoffset if trace_tweaking then report_tweak("taking %U from %U",target,original,weird,normal) end end end fix(snormal,sraised) fix(tnormal,traised) 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, unicode = unicode, commands = { { "offset", 0, offset, template, scale, 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 local reported = { } function mathtweaks.version(target,original,parameters) local metadata = original.shared.rawdata.metadata if metadata then local version = string.strip(metadata.version or "") -- some have trailing spaces if version then local expected = parameters.expected local fontname = metadata.fontname or false local message = parameters.message -- version = tonumber(string.match(version,"%d+.%d+")) if version ~= expected and not reported[fontname] then report_tweak("version %a found, version %a expected",target,original,version,expected) elseif trace_tweaking then report_tweak("version %a found",target,original,version) end if message and message ~= "" and not reported[fontname] then report_tweak() report_tweak("%s",target,original,message) report_tweak() end reported[fontname] = true end end end end local apply_tweaks = true directives.register("math.applytweaks", function(v) apply_tweaks = v; end) local function applytweaks(when,target,original) if apply_tweaks then local goodies = original.goodies if goodies then local tweaked = target.tweaked or { } if tweaked[when] then if trace_defining then report_math("tweaking math of %a @ %p (%s: %s)",target.properties.fullname,target.parameters.size,when,"done") end else 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: %s)",target.properties.fullname,target.parameters.size,when,"okay") 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 if original then action(target,original,tweak) else action(target,tweak) end end end end end end end tweaked[when] = true target.tweaked = tweaked end end else report_math("not tweaking math of %a @ %p (%s)",target.properties.fullname,target.parameters.size,when) end end function mathematics.tweakbeforecopyingfont(target,original) if use_math_goodies then local mathparameters = target.mathparameters -- why not hasmath if mathparameters then applytweaks("beforecopying",target,original) end end end function mathematics.tweakaftercopyingfont(target,original) if use_math_goodies then local mathparameters = target.mathparameters -- why not hasmath if mathparameters then applytweaks("aftercopying",target,original) end end end function mathematics.beforepassingfonttotex(target) if use_math_goodies then local mathparameters = target.mathparameters -- why not hasmath if mathparameters then applytweaks("beforepassing",target,target) end end end sequencers.appendaction("mathparameters","system","mathematics.overloadparameters") sequencers.appendaction("mathparameters","system","mathematics.scaleparameters") sequencers.appendaction("mathparameters","system","mathematics.checkaccentbaseheight") -- should go in lfg instead ----------.appendaction("mathparameters","system","mathematics.checkprivateparameters") -- after scaling ! sequencers.appendaction("beforecopyingcharacters","system","mathematics.tweakbeforecopyingfont") sequencers.appendaction("aftercopyingcharacters", "system","mathematics.tweakaftercopyingfont") sequencers.appendaction("beforepassingfonttotex", "system","mathematics.beforepassingfonttotex") -- no, it's a feature now (see good-mth): -- -- sequencers.appendaction("aftercopyingcharacters", "system","mathematics.overloaddimensions") -- helpers local getfontoffamily = tex.getfontoffamily local fontcharacters = fonts.hashes.characters 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 } -- top curly bracket: 23DE 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.hparts then if character.vparts 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.vparts 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].hparts 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].hparts 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].hparts 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, } -- todo: run this directly .. can be done in luametatex 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) -- compact: size = 655360 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 -- compact: size = 655360 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