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, concat = table.fastcopy, table.copy, table.insert, table.remove, table.concat local formatters = string.formatters local byte = string.byte local setmetatableindex, sortedkeys, sortedhash = table.setmetatableindex, table.sortedkeys, 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 getfontoffamily = tex.getfontoffamily local texget = tex.get local fontcharacters = fonts.hashes.characters local chardata = characters.data local extensibles = mathematics.extensibles local context = context local commands = commands local mathematics = mathematics local texsetdimen = tex.setdimen local texisdimen = tex.isdimen 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 d_scratchleftoffset = texisdimen("scratchleftoffset") local d_scratchrightoffset = texisdimen("scratchrightoffset") local use_math_goodies = true directives.register("math.nogoodies", function(v) use_math_goodies = not v end) local checkitalics = false trackers .register("math.checkitalics", function(v) checkitalics = 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 -- we need a better reset because the following will scale 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 = mathparameters.SubscriptShiftDown * 1.5 end -- 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 = 0 end if not mathparameters.PrimeRaiseComposedPercent then mathparameters.PrimeRaiseComposedPercent = 0 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 if not mathparameters.RadicalKernAfterExtensible then mathparameters.RadicalKernAfterExtensible = 0 end if not mathparameters.RadicalKernBeforeExtensible then mathparameters.RadicalKernBeforeExtensible = 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", -- RadicalRuleThickness = "vertical", OverbarRuleThickness = "vertical", FractionRuleThickness = "vertical", UnderbarRuleThickness = "vertical", } local function scaleparameters(mathparameters,parameters) if mathparameters and next(mathparameters) and parameters then 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 end function mathematics.scaleparameters(target,original) if not target.properties.math_is_scaled then scaleparameters(target.mathparameters,target.parameters) 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 -- if trace_defining then -- report_math("zero AccentBaseHeight corrected %a @ %p",target.properties.fullname,target.parameters.size) -- end -- mathparameters.AccentBaseHeight = target.parameters.xheight -- needs checking -- 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 datasets = { } local mathtweaks = { datasets = datasets } mathematics.tweaks = mathtweaks -- can be a common helper: local f_u = formatters["%U"] local function unicodecharlist(t) local r = { } local n = 0 for u in sortedhash(t) do n = n + 1 ; r[n] = f_u(u) end return concat(r," ") end 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 if parameters then 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 print("something is wrong") end else report_mathtweak("") end end local function feedback_tweak(tweak,target,original,done) if not done or (type(done) == "table" and not next(done)) then if trace_tweaking then -- for now report_tweak("no need for %a",target,original,tweak) end elseif trace_tweaking then report_tweak("tweak %a applied to: %s",target,original,tweak,unicodecharlist(done)) end end 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 }, dotless = { 0x00049, 0x0004A, 0x00131, 0x00237, 0x1D6A4, 0x1D6A5 }, integrals = { 0x0222B, 0x0222C, 0x0222D, 0x0222E, 0x0222F, 0x02230, 0x02231, 0x02232, 0x02233, 0x02A0B, 0x02A0C, 0x02A0D, 0x02A0E, 0x02A0F, 0x02A10, 0x02A11, 0x02A12, 0x02A13, 0x02A14, 0x02A15, 0x02A16, 0x02A17, 0x02A18, 0x02A19, 0x02A1A, 0x02A1B, 0x02A1C, 0x02320, 0x02321 }, horizontalfences = { 0x023B4, 0x023B5, 0x023DC, 0x023DD, 0x023DE, 0x023DF, 0x023E0, 0x023E1 }, -- not really used } local function getalso(target,original) local also = target.tweakalso -- maybe if not also then also = { } -- for k, v in sortedhash(target.characters) do for k, v in next, target.characters do local u = v.unicode if u and k ~= u then local a = also[u] if a then a[#a+1] = k else also[u] = { k } end end end target.tweakalso = also end return also 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.parts 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 -- This temporary tweak was used when we (MS & HH) were fixing the Latin Modern -- parameters that relate to script placement. We started from the original cmr -- ratios combined with the formal specification and ended up with the following -- values. In the end we rejected this tweak and setteled for checking and fixing: -- -- SubscriptShiftDown -- SubscriptShiftDownWithSuperscript -- SuperscriptShiftUp -- SuperscriptShiftUpCramped -- -- because it looks a bit arbitrary what values are set. We keep the code below -- as documentation. -- do -- -- modern -- -- -- -- local factors = { -- -- scripts = { -- -- SubscriptBaselineDropMin = 0.116, -- -- SubscriptShiftDown = 0.348, -- -- SubscriptShiftDownWithSuperscript = 0.573, -- -- SubscriptTopMax = 0.800, -- -- SuperscriptBaselineDropMax = 0.896, -- -- SuperscriptBottomMaxWithSubscript = 0.800, -- -- SuperscriptBottomMin = 0.250, -- -- SuperscriptShiftUp = 0.958, -- -- SuperscriptShiftUpCramped = 0.958, -- -- } -- -- } -- -- -- -- cambria -- -- -- -- local factors = { -- -- scripts = { -- -- SubscriptBaselineDropMin = 0.279, -- -- SubscriptShiftDown = 0.364, -- -- SubscriptShiftDownWithSuperscript = 0.547, -- -- SubscriptTopMax = 0.662, -- -- SuperscriptBaselineDropMax = 0.401, -- -- SuperscriptBottomMaxWithSubscript = 0.667, -- -- SuperscriptBottomMin = 0.208, -- -- SuperscriptShiftUp = 0.654, -- -- SuperscriptShiftUpCramped = 0.654, -- -- } -- -- } -- -- -- after some tests and inspection -- -- -- -- local factors = { -- scripts = { -- SubscriptBaselineDropMin = 0.100, -- harmless but small (seldom triggered) -- SubscriptShiftDown = 0.400, -- by inspection in several files -- SubscriptShiftDownWithSuperscript = 0.400, -- as above -- SubscriptTopMax = 0.800, -- Microsoft recommendation -- SuperscriptBaselineDropMax = 0.100, -- see SubscriptBaselineDropMin -- SuperscriptBottomMaxWithSubscript = 0.800, -- Microsoft recommendation -- SuperscriptBottomMin = 0.250, -- Microsoft recommendation -- SuperscriptShiftUp = 0.650, -- by inspection, but also a bit gamble -- SuperscriptShiftUpCramped = 0.650, -- see above, non-TeX -- } -- } -- -- datasets.fixparameters = factors -- -- function mathtweaks.fixparameters(target,original,parameters) -- local mathparameters = target.mathparameters -- if mathparameters and next(mathparameters) then -- local xheight = target.parameters.xheight -- -- todo : options -- for k, v in next, factors.scripts do -- mathparameters[k] = v * xheight -- end -- 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 axis = tonumber(v.axis) if axis then axis = target.mathparameters.AxisHeight * axis end 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 originalslot = v.original if not originalslot then local delta = v.delta if delta then originalslot = k + delta end end if originalslot then originalslot = mathgaps[originalslot] or originalslot local data = targetcharacters[originalslot] if data then data = copytable(data) data.unicode = originalslot targetcharacters[k] = data character = data else report_mathtweak("no slot %U",originalslot) return end end -- local width = character.width local height = character.height local depth = character.depth local italic = character.italic local topanchor = character.topanchor local bottomanchor = character.bottomanchor -- 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 = character.advance or 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 axis then character.height = (character.height or 0) - axis character.depth = (character.depth or 0) + axis character.yoffset = (character.yoffset or 0) + axis 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.topanchor = anchorfactor * (topanchor or width) end -- if anchorfactor then -- character.bottomaccent = anchorfactor * (bottomanchor 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.parts 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 function mathtweaks.showinfo(target,original,parameters) local mathparameters = target.mathparameters for k, v in sortedhash(mathparameters) do report_mathtweak("%s : %s",k,v) end 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 -- local also = getalso(target,original) local done = false for k, v in sortedhash(list) do local ori = targetcharacters[k] local nxt = ori.next local cnt = v if nxt then local prt = nil local lst = { } while nxt do local chr = targetcharacters[nxt] lst[#lst+1] = chr nxt = chr.next if not nxt then prt = chr.parts break end end if prt then count = count + 1 if cnt ~= "*" then if #lst < cnt then cnt = #lst end ori = lst[cnt] end ori.parts = prt end if not trace_tweaking then done = true elseif done then done[k] = true else done = { [k] = true } end end end feedback_tweak("wipevariants",target,original,done) end end end do function mathtweaks.replace(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_tweak("%i permanent replacements",target,original,count) end end end end function mathtweaks.substitute(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_tweak("%i permanent substitutions",target,original,count) end end end end do -- maybe we'll have a different name function mathtweaks.kernpairs(target,original,parameters) local list = parameters.list if list then local targetcharacters = target.characters local originalcharacters = original.characters local done = false 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 if not trace_tweaking then done = true elseif done then done[t] = true else done = { [t] = true } end end end) end chardata.kerns = kerns end end end for k, v in next, 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 feedback_tweak("kernpairs",target,original,done) end end end do local nps = fonts.helpers.newprivateslot local list = { { 0x2032, nps("delimited ghost 0x2032"), 1 }, { 0x2033, nps("delimited ghost 0x2033"), 2, 0x2032 }, { 0x2034, nps("delimited ghost 0x2034"), 3, 0x2032 }, { 0x2057, nps("delimited ghost 0x2057"), 4, 0x2032 }, { 0x2035, nps("delimited ghost 0x2035"), 1 }, { 0x2036, nps("delimited ghost 0x2036"), 2, 0x2035 }, { 0x2037, nps("delimited ghost 0x2037"), 3, 0x2035 }, } datasets.fixprimes = list function mathtweaks.fixprimes(target,original,parameters) local targetcharacters = target.characters local factor = parameters.factor or 1 local fake = tonumber(parameters.fake) for i=1,#list do local entry = list[i] local unicode = entry[1] local count = entry[3] local used = fonts.handlers.otf.getsubstitution(target,unicode,"ssty",true,"math","dflt") or unicode local data = targetcharacters[used] if data then targetcharacters[unicode] = data local oldheight = data.height or 0 local newheight = factor * oldheight data.yoffset = newheight - (oldheight or 0) data.height = newheight data.smaller = nil elseif not fake then report_tweak("missing %i prime %U",target,original,count,unicode) end end if fake then for i=1,#list do local entry = list[i] local count = entry[3] if count > 1 then local unicode = entry[1] local original = entry[4] local data = targetcharacters[original] if data then local oldwidth = data.width local xoffset = fake * oldwidth local newwidth = oldwidth + (count - 1) * xoffset targetcharacters[unicode] = { width = newwidth, height = data.height, unicode = unicode, commands = { { "offset", 0, 0, original }, { "offset", xoffset, 0, original }, count > 2 and { "offset", 2 * xoffset, 0, original } or nil, count > 3 and { "offset", 3 * xoffset, 0, original } or nil, }, } end end end end end function mathtweaks.addprimed(target,original,parameters) local characters = target.characters for i=1,#list do local entry = list[i] local basecode = entry[1] local movecode = entry[2] local basedata = characters[basecode] if basedata then local baseheight = basedata.height or 0 local basewidth = basedata.width or 0 local used = baseheight local total = baseheight characters[movecode] = { -- todo:share width = basewidth, height = used, unicode = basecode, -- 0xFFFD or space or so -- callback = "devirtualize", commands = { downcommand[used], { "rule", used, 0 }, }, } basedata.partsorientation = "vertical" basedata.parts = { { advance = used, ["end"] = used, extender = 1, glyph = movecode, start = used, }, { advance = total, ["end"] = 0, glyph = basecode, start = total, }, } if trace_tweaking then report_tweak("primed %U added",target,original,basecode) end 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 -- local done = false -- for k, v in next, targetcharacters do -- local a = v.topanchor -- if a and a > 0 then -- v.topanchor = a * factor -- count = count + 1 -- if not trace_tweaking then -- done = true -- elseif done then -- done[u] = true -- else -- done = { [u] = true } -- end -- end -- end -- end -- feedback_tweak("fixanchors",target,original,done) -- end -- -- end -- do -- -- -- actually this should be a an engine feature driven by category because we don't -- -- want this in display mode .. only a test for MS and HH -- -- local issymbol = characters.is_symbol -- -- function mathtweaks.oldstylemath(target,original,parameters) -- local chardata = characters.data -- local characters = target.characters -- local axis = target.mathparameters.AxisHeight -- local delta = (parameters.factor or .1) * axis -- target.mathparameters.AxisHeight = (axis - delta) -- for k, v in sortedhash(characters) do -- if issymbol[k] then -- quick hack, engine knows -- v.yoffset = -delta -- v.height = (v.height or 0) - delta -- v.depth = (v.depth or 0) - delta -- end -- end -- end -- -- function mathtweaks.oldstylemath(target,original,parameters) -- -- not relevant -- end -- -- end do function mathtweaks.simplifykerns(target,original,parameters) local characters = target.characters local done = false -- for u, v in sortedhash(characters) do for u, v in next, characters do local mathkerns = v.mathkerns if mathkerns then local k = mathkerns.topleft if k then k = k[#k].kern if k then v.topleft = k end end local k = mathkerns.topright if k then k = k[#k].kern if k then v.topright = k end end local k = mathkerns.bottomleft if k then k = k[1].kern -- todo get value at baseline if k then v.bottomleft = k end end local k = mathkerns.bottomright if k then k = k[1].kern -- todo get value at baseline if k then v.bottomright = k end end v.mathkerns = nil if not trace_tweaking then done = true elseif done then done[u] = true else done = { [u] = true } end end end feedback_tweak("simplifykerns",target,original,done) end end do local function wipe(whatever,target,original,parameters,field,move,integrals) local targetcharacters = target.characters local targetdescriptions = target.descriptions local factor = target.parameters.factor local correct = parameters.correct local done = false local function getllx(u) local d = targetdescriptions[u] if d then local b = d.boundingbox if b then local llx = b[1] if llx < 0 then return - llx end end end return false end local function step(s) while s do local u = mathgaps[s] or s local c = targetcharacters[u] if c then if field == "topanchor" then if c.topanchor then c.topanchor = nil else goto smaller end else local okay = false local italic = c.italic if move and not c.advance then -- advance check prevents double move local width = c.width or 0 c.advance = width if correct then local llx = getllx(u) if llx then local topanchor = c.topanchor llx = llx * factor width = width + llx c.xoffset = llx if topanchor then c.topanchor = topanchor + llx end -- too bad (schola e^x): -- c.bottomleft = (c.bottomleft or 0) - llx -- c.topleft = (c.topleft or 0) - llx okay = true end end if italic and italic ~= 0 then c.width = width + italic c.bottomright = - italic okay = true else c.width = width end end if italic then c.italic = nil okay = true end if okay then if not trace_tweaking then done = true elseif done then done[u] = true else done = { [u] = true } end else goto smaller end end goto smaller ::smaller:: s = c.smaller ::variants:: -- no italics here anyway but we could check them some day else break end end end local list = parameters.list -- todo: ranges if list == "letters" or parameters.letters then local chardata = characters.data -- for k, v in sortedhash(targetcharacters) do for k, v in next, targetcharacters do if v.italic then local d = chardata[v.unicode] local c = d and d.category if c == "ll" or c == "lu" then step(k) end end end return elseif not list or list == "all" or list == true or parameters.all then list = sortedkeys(targetcharacters) elseif type(list) == "string" then list = { list } end for i=1,#list do local l = list[i] local t = type(l) if not l then -- can be false elseif t == "table" then for i=1,#l do step(l[i]) end elseif t == "number" then step(l) else local r = blocks[l] if r then for i=r.first,r.last do step(i) end else stepper(l,step) end end end feedback_tweak(whatever,target,original,done) end function mathtweaks.wipeanchors(target,original,parameters) wipe("wipeanchors",target,original,parameters,"topanchor") end function mathtweaks.wipeitalics(target,original,parameters) if not checkitalics then wipe("wipeitalics",target,original,parameters,"italic") end end function mathtweaks.moveitalics(target,original,parameters) wipe("moveitalics",target,original,parameters,"italic",true) 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 function mathtweaks.topanchors(target,original,parameters) local characters = target.characters local list = parameters.list if list then local done = false for u, v in sortedhash(list) do local c = characters[k] if c then local w = c.width if w and w ~= 0 then c.topanchor = v * w if trace_tweaking then -- todo end if not trace_tweaking then done = true elseif done then done[u] = true else done = { [u] = true } end end end end feedback_tweak("topanchors",target,original,done) end end function mathtweaks.movelimits(target,original,parameters) local characters = target.characters local list = parameters.list if list then local factor = parameters.factor or 1 local also = getalso(target,original) local done = { } local function relocate(u,factor) if done[u] then return end done[u] = true local c = characters[u] if c then local italic = c.italic if italic then if italic ~= 0 then local width = c.width or 0 local half = (italic/2) * factor c.topanchor = width + half c.bottomanchor = width - half c.bottomright = - italic * (parameters.icfactor or 1) if trace_tweaking then -- todo end end c.italic = nil end local s = c.smaller if s then relocate(s,factor) end local n = c.next if n then relocate(n,factor) end -- Kind of tricky: we configure the engine to use the vitalic -- so when we tweak we need to set that to zero. local parts = c.parts local italic = c.partsitalic if parts and italic then if italic ~= 0 then local tchar = characters[parts[#parts].glyph] local bchar = characters[parts[1].glyph] local width = tchar.width or 0 local half = (italic/2) * factor tchar.topanchor = width + half bchar.bottomanchor = width - half bchar.bottomright = - italic if trace_tweaking then -- todo end tchar.italic = nil bchar.italic = nil end c.vitalic = nil end if also then local a = also[u] if a then for i=1,#a do relocate(a[i],factor) end end end end end if #list > 0 then for i=1,#list do relocate(list[i],factor) end else for k, v in sortedhash(list) do relocate(k,tonumber(v) or factor) end end feedback_tweak("movelimits",target,original,done) end 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 done = false 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 if not trace_tweaking then done = true elseif done then done[unicode] = true else done = { [unicode] = true } 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 feedback_tweak("kerns",target,original,done) end end end do function mathtweaks.margins(target,original,parameters) local margins = parameters.list if margins then local characters = target.characters local done = false local function setone(unicode,data) unicode = mathgaps[unicode] or unicode local chardata = characters[unicode] if chardata then 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 if not trace_tweaking then done = true elseif done then done[unicode] = true else done = { [unicode] = true } end end end for unicode, data in next, margins do setone(unicode,data) -- withscriptcode(tfmdata,unicode,data,kernone) -- also smaller end feedback_tweak("margins",target,original,done) 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 }, } datasets.accentdimensions = candidates 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.parts 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 } }, -- parts = { -- { 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 } }, -- parts = { -- { 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 } }, parts = { { 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 } }, parts = { { 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 } }, parts = { { 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 overloads[unicode] == false or not chardata.parts) then if not list then chardata.parts = nil -- when we test else local overload = overloads[unicode] local parts = { } 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 parts[#parts+1] = { advance = width, glyph = glyph, ["end"] = step, start = step, extender = 1, } else parts[#parts+1] = { advance = width, glyph = glyph, ["end"] = 0, start = step, } end end if #parts == #list then chardata.parts = parts 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 datasets.addarrows = { } 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 datasets.addarrows = sortedkeys(arrows) if trace_tweaking then report_tweak("arrows added",target,original) 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 if sequence then local parts = source.parts 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.parts = p if not data.horizontal then target.partsorientation = "vertical" end 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 } datasets.checkspacing = list 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 -- mirror -- smaller 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 if data.rorrim then -- the original does the magic else data.yoffset = depth - height end end local smaller = data.smaller if smaller then fix(target,original,characters,smaller) end local mirror = data.mirror if mirror then fix(target,original,characters,mirror) 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 local function fix(target,original,characters,u,l) local data = characters[u] if data then data.innerlocation = l.location == "right" and 2 or 1 data.innerxoffset = (l.hfactor or 1) * (data.width or 0) data.inneryoffset = (l.vfactor or 1) * ((data.height or 0) + (data.depth or 0)) end end function mathtweaks.radicaldegreeanchors(target,original,parameters) local list = parameters.list if list then local characters = target.characters for unicode, l in sortedhash(list) do -- resolve variants local u = detail(characters,unicode) or unicode if type(u) == "table" then for i=1,#u do fix(target,original,characters,u[i],l) end else fix(target,original,characters,u,l) end end end end end do local done = nil local function fix(target,original,characters,unicode,axis) if done[unicode] then return end done[unicode] = true local data = characters[unicode] if data then local height = data.height or 0 local depth = data.depth or 0 if trace_tweaking then report_tweak("swapping height and depth of %U",target,original,unicode) end local half = (height + depth)/2 if data.rorrim then -- the original does the magic else data.yoffset = depth - (half - axis) end height = half + axis depth = half - axis data.height = height data.depth = depth local smaller = data.smaller if smaller then fix(target,original,characters,smaller,axis) end local mirror = data.mirror if mirror then fix(target,original,characters,mirror,axis) end local next = data.next if next then fix(target,original,characters,next,axis) end end end function mathtweaks.fixoldschool(target,original,parameters) local characters = target.characters local list = mathtweaks.subsets.integrals local also = getalso(target,original) local axis = target.mathparameters.AxisHeight done = { } for i=1,#list do local unicode = list[i] fix(target,original,characters,unicode,axis) end if also then local a = also[u] if a then for i=1,#a do fix(target,original,characters,a[i],axis) end end end done = nil end -- After the next one I rewarded myself by (again) watching Joe Parrish interpretation -- of Shostakovich 10 Mvmt. II - Metal several times (video on yt, track on bandcamp) -- ... timestamp: awaiting the new Albion (Official) single; their work comes in parts. function mathtweaks.fixintegrals(target,original,parameters) local characters = target.characters local integral = characters[0x222B] if integral and not integral.parts then local top = characters[0x2320] local mid = characters[0x23AE] local bot = characters[0x2321] if top and mid and bot then top = top.height mid = mid.height bot = bot.height integral.partsitalic = integral.italic integral.parts = { { advance = bot, ["end"] = bot/3, glyph = 0x2321, start = bot/3 }, { advance = mid, ["end"] = mid/2, glyph = 0x23AE, start = mid/2, extender = 1 }, { advance = top, ["end"] = top/3, glyph = 0x2320, start = top/3 }, } integral.partsorientation = "vertical" if trace_tweaking then report_tweak("fixing the integral extensible",target,original) end end else report_tweak("no need to fix the integral extensible",target,original) end end end do local list = { 0x2061, 0x2062, 0x2063, 0x2064 } datasets.wipecues = list function mathtweaks.wipecues(target,original,parameters) local characters = target.characters local tobewiped = parameters.list or list local done = false for i=1,#tobewiped do local unicode = tobewiped[i] characters[unicode] = { width = 0, height = 0, depth = 0, unicode = unicode, } if not trace_tweaking then done = true elseif done then done[unicode] = true else done = { [unicode] = true } end end feedback_tweak("wipecues",target,original,done) end end do local mapping = { [0x002F] = 0x2044, } datasets.fixslashes = mapping function mathtweaks.fixslashes(target,original,parameters) local characters = target.characters -- local done = false 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 not trace_tweaking then -- done = true -- elseif done then -- done[normal] = true -- else -- done = { [normal] = true } -- end end weirdone = copytable(normalone) characters[weird] = weirdone weirdone.unicode = weird end -- feedback_tweak("fixslashes",target,original,done) if trace_tweaking then report_tweak("slashes fixed",target,original) end end end do -- see pagella for an extensive example local nps = fonts.helpers.newprivateslot local mapping = { [0x0300] = { 0x0060, false, nps("flat 0x0060 1") }, [0x0308] = { 0x00A8, false, nps("flat 0x00A8 1") }, [0x0304] = { 0x00AF, false, nps("flat 0x00AF 1") }, [0x0301] = { 0x00B4, false, nps("flat 0x00B4 1") }, [0x0302] = { 0x02C6, true, nps("flat 0x02C6 1") }, [0x030C] = { 0x02C7, true, nps("flat 0x02C7 1") }, [0x0306] = { 0x02D8, false, nps("flat 0x02D8 1") }, [0x0307] = { 0x02D9, false, nps("flat 0x02D9 1") }, [0x030A] = { 0x02DA, false, nps("flat 0x02DA 1") }, [0x0303] = { 0x02DC, true, nps("flat 0x02DC 1") }, [0x20DB] = { 0x20DB, false, nps("flat 0x20DB 1") }, } datasets.fixaccents = mapping datasets.extendaccents = mapping datasets.flattenaccents = mapping datasets.copyaccents = mapping function mathtweaks.fixaccents(target,original,parameters) local characters = target.characters local done = false for stretching, entry in sortedhash(mapping) do local alias = entry[1] local stretchingdata = characters[stretching] if stretchingdata and stretchingdata.width == 0 then local topanchor = stretchingdata.topanchor or 0 local width = -topanchor topanchor = width/2 stretchingdata.width = width stretchingdata.advance = 0 stretchingdata.topanchor = topanchor stretchingdata.commands = { rightcommand[width + topanchor], charcommand[stretching] } if not trace_tweaking then done = true elseif done then done[stretching] = true else done = { [stretching] = true } end end end feedback_tweak("fixaccents",target,original,done) end -- all true|number false function mathtweaks.extendaccents(target,original,parameters) local characters = target.characters local all = parameters.all local count = tonumber(all) local done = false for stretching, entry in sortedhash(mapping) do local extend = entry[2] if extend then local last = characters[stretching] local cnt = 1 local okay = false while last do if all or (count and cnt > count) then last.extensible = true local flataccent = last.flataccent if flataccent then characters[flataccent].extensible = true okay = true end end 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 okay = true end break end cnt = cnt + 1 end if okay then if not trace_tweaking then done = true elseif done then done[stretching] = true else done = { [stretching] = true } end end end end feedback_tweak("extendaccents",target,original,done) end -- force true false -- height factor 0.8 -- offset factor 0.9|calculated -- squeeze factor 0.1|calculated function mathtweaks.flattenaccents(target,original,parameters) local characters = target.characters local force = parameters.force local squeeze = parameters.squeeze or 0.8 local ofactor = parameters.offset or (squeeze/2) local hfactor = parameters.height or (1 - ofactor) local done = false for stretching, entry in sortedhash(mapping) do local last = characters[stretching] while last do if force or not last.flataccent then local slot = entry[3] local data = copytable(last) local height = data.height or 0 data.effect = { squeeze = squeeze } data.height = hfactor * height data.yoffset = ofactor * height characters[slot] = data last.flataccent = slot if not trace_tweaking then done = true elseif done then done[stretching] = true else done = { [stretching] = true } end end local n = last.next if n then last = characters[n] else break end end end feedback_tweak("flattenaccents",target,original,done) end function mathtweaks.copyaccents(target,original,parameters) local characters = target.characters local done = false 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] }, topanchor = stretchingdata.topanchor, -- unicode = stretching, -- when we alias to combiners unicode = alias, -- when we keep the original } if not trace_tweaking then done = true elseif done then done[stretching] = true else done = { [stretching] = true } end end end end feedback_tweak("copyaccents",target,original,done) 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 or 0)/2 local basedepth = (basechar.depth or 0)/2 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 }, }, parts = { { 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 }, }, parts = { { 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 = { -- { 0x0300, nps("delimited right grave"), nps("delimited ghost grave") }, { 0x0308, nps("delimited right ddot"), nps("delimited ghost ddot") }, { 0x0304, nps("delimited right bar"), nps("delimited ghost bar") }, -- { 0x0301, nps("delimited right acute"), nps("delimited ghost acute") }, { 0x0302, nps("delimited right hat"), nps("delimited ghost hat") }, { 0x030C, nps("delimited right check"), nps("delimited ghost check") }, { 0x0306, nps("delimited right breve"), nps("delimited ghost breve") }, { 0x0307, nps("delimited right dot"), nps("delimited ghost dot") }, { 0x030A, nps("delimited right ring"), nps("delimited ghost ring") }, { 0x0303, nps("delimited right tilde"), nps("delimited ghost tilde") }, { 0x20DB, nps("delimited right dddot"), nps("delimited ghost dddot") }, { 0x2020, nps("delimited right dagger"), nps("delimited ghost dagger") }, { 0x2021, nps("delimited right ddagger"), nps("delimited ghost ddagger") }, { 0x2217, nps("delimited right ast"), nps("delimited ghost ast") }, { 0x22C6, nps("delimited right star"), nps("delimited ghost star") }, { 0x231C, nps("delimited left upper corner"), nps("delimited ghost upper corner") }, { 0x231D, nps("delimited right upper corner"), nps("delimited ghost upper corner") }, { 0x231E, nps("delimited left lower corner"), nps("delimited ghost lower corner"), true }, { 0x231F, nps("delimited right lower corner"), nps("delimited ghost lower corner"), true }, } 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 reverse = entry[4] local basechar = characters[basecode] if basechar then 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 if reverse then used = total / 2 -- basedepth / 2 end characters[movecode] = { width = basewidth, height = used, unicode = basecode, -- callback = "devirtualize", commands = { downcommand[used], { "rule", used, 0 }, }, } local parts = { { advance = used, ["end"] = used, extender = 1, glyph = movecode, start = used, }, { advance = total, ["end"] = 0, glyph = fouriercode, start = total, }, } if reverse then parts[1], parts[2] = parts[2], parts[1] end characters[fouriercode] = { width = basewidth, height = baseheight, depth = basedepth, unicode = basecode, -- callback = "devirtualize", commands = { scale == 1 and charcommand[basecode] or { "slot", 0, basecode, scale, scale }, }, partsorientation = "vertical", parts = parts, } if trace_tweaking then report_tweak("fourier %U added using %U",target,original,basecode,fouriercode) end 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 basechar.parts = extensible(single,total,used) basechar.partsorientation = "vertical" characters[double] = { unicode = double, width = 2*width - 1*advance, height = height, depth = depth, parts = extensible(double,total,used), partsorientation = "vertical", callback = "devirtualize", commands = { charcommand[single], leftcommand[advance], charcommand[single], }, } characters[triple] = { unicode = triple, width = 3*width - 2*advance, height = height, depth = depth, parts = extensible(triple,total,used), partsorientation = "vertical", 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 normaldata = characters[normal] if normaldata then local raiseddata = copytable(normaldata) characters[raised] = raiseddata raiseddata.unicode = raised local height = raiseddata.height local yoffset = (parameters.yoffset or 2) * height raiseddata.yoffset = yoffset raiseddata.height = height + yoffset if trace_tweaking then report_tweak("taking %U from %U",target,original,raised,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 }, } datasets.addscripts = list 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 -- We started with the list that xits has in rtlm but most of them can be derived from -- the database, and others need to be added. -- Checked while watching/listening to Dave Matthews Band: The Central Park Concert -- (with superb solos by Warren Haynes), a DVD I bought around when we started with the -- LUATEX advanture. local mirrors = { [0x0002F] = true, -- slashes [0x0005C] = true, [0x000F7] = true, [0x02044] = true, [0x02215] = true, [0x02032] = true, -- primes [0x02033] = true, [0x02034] = true, [0x02057] = true, [0x02035] = true, [0x02036] = true, [0x02037] = true, [0x0221A] = true, -- radicals [0x0221B] = true, [0x0221C] = true, [0x0221D] = true, [0x0222B] = true, -- integrals [0x0222C] = true, [0x0222D] = true, [0x0222E] = true, [0x0222F] = true, [0x02230] = true, [0x02231] = true, [0x02232] = true, [0x02233] = true, [0x02A0A] = true, -- seen in xits (to be checked) [0x02A0B] = true, [0x02A0C] = true, [0x02A0D] = true, [0x02A0E] = true, [0x02140] = true, [0x02201] = true, [0x02202] = true, [0x02203] = true, [0x02204] = true, [0x02211] = true, [0x02239] = true, [0x0225F] = true, [0x0228C] = true, [0x022A7] = true, [0x022AA] = true, [0x022AC] = true, [0x022AD] = true, [0x022AE] = true, [0x022AF] = true, [0x022F5] = true, [0x022F8] = true, [0x022F9] = true, [0x022FF] = true, [0x02320] = true, [0x02321] = true, [0x027C0] = true, [0x029DC] = true, [0x029F4] = true, [0x02A0F] = true, [0x02A10] = true, [0x02A11] = true, [0x02A12] = true, [0x02A13] = true, [0x02A14] = true, [0x02A15] = true, [0x02A16] = true, [0x02A17] = true, [0x02A18] = true, [0x02A19] = true, [0x02A1A] = true, [0x02A1B] = true, [0x02A1C] = true, [0x02A20] = true, [0x02A74] = true, [0x02AA3] = true, [0x02AE2] = true, [0x02AE6] = true, [0x1D715] = true, } local new = fonts.helpers.newprivateslot local function add(target,original,characters,unicode,what) local data = characters[unicode] if data then if not data.mirror then local slot = new("mirror."..unicode) local mirror = copytable(data) data.mirror = slot mirror.rorrim = unicode -- so we can check later mirror.commands = { { "offset", data.width, 0, unicode, -1, 1 } } if trace_tweaking then report_tweak("adding mirror %U (%s)",target,original,unicode,what) end characters[slot] = mirror elseif trace_tweaking then report_tweak("skipping mirror %U (%s)",target,original,unicode,what) end local parts = data.parts if parts then for i=1,#parts do add(target,original,characters,parts[i],"hpart") end end local smaller = data.smaller if smaller then add(target,original,characters,"smaller") end local next = data.next if next then add(target,original,characters,next,"next") end end end -- todo: also check the rtlm table if present function mathtweaks.addmirrors(target,original,parameters) local characters = target.characters -- for unicode, detail in sortedhash(characters) do for unicode, detail in next, characters do local data = chardata[unicode] if data and data.mirror then add(target,original,characters,unicode,"mirror") end end for unicode, detail in sortedhash(mirrors) do if characters[unicode] then add(target,original,characters,unicode,"character") elseif trace_tweaking then report_tweak("ignoring mirror %U (%s)",target,original,unicode,what) 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 target.tweakversion = version end end end end do function mathtweaks.parameters(target,original,parameters) local newparameters = parameters.list local oldparameters = target.mathparameters if newparameters and oldparameters then newparameters = copytable(newparameters) scaleparameters(newparameters,target.parameters) for name, newvalue in next, newparameters do oldparameters[name] = newvalue end end end function mathtweaks.bigslots(target,original,parameters) local list = parameters.list if list then target.bigslots = list end end end -- do -- -- function mathtweaks.diagnose(target,original,parameters) -- local characters = target.characters -- for k, v in sortedhash(characters) do -- local italic = v.italic -- if italic then -- report_tweak("italics: %C %p",target,original,k,italic) -- end -- end -- end -- -- end do function mathtweaks.setoptions(target,original,parameters) local setlist = parameters.set or parameters.list local resetlist = parameters.reset if setlist or resetlist then local properties = target.properties local codes = tex.mathcontrolcodes local oldcontrol = texget("mathfontcontrol") local newcontrol = oldcontrol -- todo: reset if resetlist then for i=1,#resetlist do local v = tonumber(codes[resetlist[i]]) if v then newcontrol = newcontrol & (not v) end end end if setlist then for i=1,#setlist do local v = tonumber(codes[setlist[i]]) if v then newcontrol = newcontrol | v end end end newcontrol = newcontrol | codes.usefontcontrol properties.mathcontrol = newcontrol target.mathcontrol = newcontrol if trace_tweaking then report_tweak("forcing math font options 0x%08X instead of 0x08X",target,original,newcontrol,oldcontrol) end end end end do function mathtweaks.setovershoots(target,original,parameters) local list = parameters.list if list then local characters = target.characters local emwidth = target.parameters.quad local done = false for i=1,#list do local entry = list[i] local target = entry.target local top = entry.topovershoot local quad = entry.quad if target and top then local range = blocks[target] if range then if quad then quad = emwidth end for r = range.first, range.last do local unicode = mathgaps[r] or r local data = characters[unicode] if data then data.topovershoot = top * (quad or data.width or 0) if not trace_tweaking then done = true elseif done then done[r] = true else done = { [r] = true } end end end end end end feedback_tweak("setovershoots",target,original,done) end end -- there is no real need for thios but let's play nice with memory anyway local efindex = 0 local effects = setmetatableindex (function (t,k) efindex = efindex + 1 local v = "tweakreplacealphabets" .. efindex local e = fonts.specifiers.presetcontext(v,"",k) -- print(k,v,e) t[k] = v return v end) function mathtweaks.replacealphabets(target,original,parameters) local list = parameters.list if list then local features = target.specification.features.normal local definedfont = fonts.definers.internal local copiedglyph = fonts.handlers.vf.math.copy_glyph -- does a deep copy, including parts and so local getsubstitution = fonts.handlers.otf.getsubstitution local fontdata = fonts.hashes.identifiers -- local fonts = target.fonts local 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 for i=1,#list do local entry = list[i] local filename = entry.filename or parameters.filename local feature = entry.feature local thesource = entry.source local thetarget = entry.target or thesource if thesource and thetarget then local sourcerange = type(thesource) == "table" and thesource or blocks[thesource] -- .gaps local targetrange = type(thetarget) == "table" and thetarget or blocks[thetarget] -- .gaps if sourcerange and targetrange then local firsttarget = targetrange.first local firstsource = sourcerange.first local lastsource = sourcerange.last or firstsource if firstsource and firsttarget then local offset = firsttarget - firstsource if filename then local rscale = entry.rscale or 1 -- todo size = size * rscale -- maybe use scale in vf command -- load font, todo: set language and script, the effect hack is ugly local fullname = filename local effect = features.effect if effect then fullname = fullname .. "*" .. effects["effect={"..effect.."}"] end local id = definedfont { name = fullname, size = size, } local chars = fontchars[id] local dropin = fontdata[id] local index = #fonts + 1 fonts[index] = { id = id, size = size } -- copy characters for s=firstsource,lastsource do local t = s + offset local sourceunicode = mathgaps[s] or s if chars[sourceunicode] then local targetunicode = mathgaps[t] or t if feature then sourceunicode = getsubstitution(dropin,sourceunicode,feature,true,"math","dflt") or sourceunicode end -- if trace_tweaking then -- report_tweak("copying %s %U from file %a to %s %U",target,original,thesource,sourceunicode,filename,thetarget,targetunicode) -- end characters[targetunicode] = copiedglyph(target,characters,chars,sourceunicode,index) end end elseif feature then for s=firstsource,lastsource do local t = s + offset local sourceunicode = mathgaps[s] or s local targetunicode = mathgaps[t] or t local variant = getsubstitution(original,sourceunicode,feature,true,"math","dflt") local data = characters[variant] if data then -- if trace_tweaking then -- report_tweak("copying %s %U from feature %a to %s %U",target,original,thesource,sourceunicode,feature,thetarget,targetunicode) -- end characters[targetunicode] = copytable(data) end end else for s=firstsource,lastsource do local t = s + offset local sourceunicode = mathgaps[s] or s local targetunicode = mathgaps[t] or t if sourceunicode ~= targetunicode then local data = characters[sourceunicode] if data then -- if trace_tweaking then -- report_tweak("copying %s %U to %s %U",target,original,thesource,sourceunicode,thetarget,targetunicode) -- end characters[targetunicode] = copytable(data) end end end end else -- error end end end end end end function mathtweaks.fallbacks(target,original,parameters) local fallbacks = target.specification.fallbacks if fallbacks then local definitions = fonts.collections.definitions[fallbacks] if definitions then local list = { } for i=1,#definitions do local definition = definitions[i] -- local check = definition.check -- local force = definition.force local first = definition.start local last = definition.stop local offset = definition.offset or first list[#list+1] = { filename = definition.font, rscale = definition.rscale or 1, source = { first = first, last = last }, target = { first = offset, last = offset + (last - first) }, } end mathtweaks.replacealphabets(target,original,{ tweak = "replacealphabets", list = list, } ) end end end end local apply_tweaks = true directives.register("math.applytweaks", function(v) apply_tweaks = v end) local applied_tweaks = 0 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 statistics.starttiming(mathtweaks) applied_tweaks = applied_tweaks + 1 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 local feature = tweak.feature local features = target.specification.features.normal if feature == nil or features[feature] then local version = tweak.version if version and version ~= target.tweakversion then report_math("skipping tweak %a version %a",tweak.tweak,version) elseif original then action(target,original,tweak) else action(target,tweak) end end end end end end end statistics.stoptiming(mathtweaks) 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 local function tweakable(target) local mathparameters = target.mathparameters -- local features = target.specification.features -- local mathscript = features and features.normal and features.normal.script == "math" -- return mathparameters and mathscript -- and target,properties.hasmath return mathparameters end function mathematics.tweakbeforecopyingfont(target,original) if use_math_goodies and tweakable(target) then applytweaks("beforecopying",target,original) end end function mathematics.tweakaftercopyingfont(target,original) if use_math_goodies and tweakable(target) then applytweaks("aftercopying",target,original) end end statistics.register("math tweaking time",function() if applied_tweaks > 0 then return string.format("%s seconds, %s math goodie tables", statistics.elapsedtime(mathtweaks),applied_tweaks) end end) do local defaults = { { source = "uppercasescript", target = "uppercasecalligraphic", }, { source = "lowercasescript", target = "lowercasecalligraphic", }, { source = "uppercaseboldscript", target = "uppercaseboldcalligraphic", }, { source = "lowercaseboldscript", target = "lowercaseboldcalligraphic", }, } local reported = table.setmetatableindex("table") function mathematics.checkaftercopyingfont(target,original) if tweakable(target) then local chardata = characters.data local characters = target.characters -- for i=1,#defaults do -- we assume no ssty here yet .. todo local default = defaults[i] local block = blocks[default.target] local first = block.first local last = block.last if not characters[mathgaps[first] or last] then mathtweaks.replacealphabets(target,original,{ tweak = "replacealphabets", list = { default } }) end end -- local addvariant = mathematics.addvariant local function register(old,new) for i, cold in next, old do local cnew = new[i] addvariant(target,cold,cold,0xFE00) addvariant(target,cnew,cnew,0xFE01) addvariant(target,cnew,cold,0xFE00) addvariant(target,cold,cnew,0xFE01) end end local sr = mathematics.alphabets.sr.tf local ca = mathematics.alphabets.ca.tf register(sr.ucletters,ca.ucletters) register(sr.lcletters,ca.lcletters) -- if checkitalics then local italics = 0 local metadata = original.shared.rawdata.metadata local fontname = metadata and metadata.fontname or false -- for k, v in sortedhash(characters) do for k, v in next, characters do local italic = v.italic if italic then local unicode = v.unicode if unicode and not reported[fontname][unicode] then -- there can be variants local data = chardata[unicode] local description = data.description or "" local category = data.category or "--" report_tweak("italics: %C %p %s %s",target,original,k,italic,category,description) reported[fontname][unicode] = true end italics = italics + 1 end end if italics > 0 then report_tweak("still has %i italics",target,original,italics) goto NEXTSTEP end end target.properties.mathitalics = false target.properties.textitalics = false ::NEXTSTEP:: -- more to come end end end function mathematics.beforepassingfonttotex(target) if tweakable(target) then applytweaks("beforepassing",target,target) end end sequencers.appendaction("mathparameters","system","mathematics.overloadparameters") sequencers.appendaction("mathparameters","system","mathematics.scaleparameters") ----------.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("aftercopyingcharacters", "system","mathematics.checkaftercopyingfont") sequencers.appendaction("beforepassingfonttotex", "system","mathematics.beforepassingfonttotex") -- no, it's a feature now (see good-mth): -- -- sequencers.appendaction("aftercopyingcharacters", "system","mathematics.overloaddimensions") -- 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.parts 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].parts 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].parts 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].parts 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(d_scratchleftoffset, loffset) texsetdimen(d_scratchrightoffset,roffset) context(kind) end } function mathematics.variantcode(unicode,variant) local data = fontcharacters[getfontoffamily(texget("fam"))] local char = data and data[unicode] if char then for i=1,variant do local next = char.next if next then unicode = next char = data[next] else break end end end return unicode end function mathematics.variantcount(unicode) local data = fontcharacters[getfontoffamily(texget("fam"))] local char = data and data[unicode] local count = 0 if char then while true do local next = char.next if next then count = count + 1 char = data[next] else break end end end return count end interfaces.implement { name = "mathvariantcode", public = true, arguments = { "integer", "integer" }, actions = { mathematics.variantcode, context }, } interfaces.implement { name = "mathvariantcount", public = true, arguments = "integer", actions = { mathematics.variantcount, context }, }