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. -- The tweaks here evolved from experiments with, discussions about and upgrades of -- the math subsystem, a project that Mikael Sundvist and I started end 2021 and -- that is still ongoing in 2023 (and probably beyond as we find new challenges as -- we go). 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 max = math.max 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 prependcommands = helpers.prependcommands local vfcommands = helpers.commands local upcommand = vfcommands.up local downcommand = vfcommands.down local rightcommand = vfcommands.right local leftcommand = vfcommands.left local slotcommand = vfcommands.slot local charcommand = vfcommands.char local push = vfcommands.push local pop = vfcommands.pop 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 function registerdone(done,unicode) if not trace_tweaking then done = true elseif done then done[unicode] = true else done = { [unicode] = true } end return done 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,nodimensions) local mathparameters = original.mathparameters if mathparameters and next(mathparameters) then if nodimensions ~= "noscale" then mathparameters = mathematics.dimensions(mathparameters) end -- -- 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.DelimiterDisplayPercent then mathparameters.DelimiterDisplayPercent = 100 end if not mathparameters.DelimiterDisplayShortfall then mathparameters.DelimiterDisplayShortfall = 0 end if not mathparameters.RadicalKernAfterExtensible then mathparameters.RadicalKernAfterExtensible = 0 end if not mathparameters.RadicalKernBeforeExtensible then mathparameters.RadicalKernBeforeExtensible = 0 end if not mathparameters.SuperscriptSnap then mathparameters.SuperscriptSnap = 750 end if not mathparameters.SubscriptSnap then mathparameters.SubscriptSnap = 250 end -- -- if mathparameters.RadicalDisplayStyleVerticalGap then -- mathparameters.RadicalDisplayStyleVerticalGap = mathparameters.RadicalDisplayStyleVerticalGap - 100 -- end -- if mathparameters.RadicalVerticalGap then -- mathparameters.RadicalVerticalGap = mathparameters.RadicalVerticalGap - 100 -- 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", DelimiterDisplayPercent = "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, 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 }, letterlike = { 0x02202 }, 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 = { 0x0203E, 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(".") local private = fonts.helpers.privateslot detail = function(characters,k) if type(k) == "string" then local t = lpegmatch(splitter,k) local n = #t if n > 0 then local slot = t[1] local base = tonumber(slot) or tonumber(slot,16) or private(slot) 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 elseif list == "flataccent" then return c.flataccent 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 pro = nil local lst = { } while nxt do local chr = targetcharacters[nxt] lst[#lst+1] = chr nxt = chr.next if not nxt then prt = chr.parts pro = chr.partsorientation 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 ori.partsorientation = pro end done = registerdone(done,k) end end feedback_tweak("wipevariants",target,original,done) end end end do -- This is a horrible tweak for lm that has bars inconsistent with other fences. local function find(targetcharacters,slot,n) local chr = targetcharacters[slot] while chr do slot = chr.next if not slot then break elseif n == 1 then return slot end n = n - 1 chr = targetcharacters[slot] end return nil end function mathtweaks.fixvariants(target,original,parameters) local list = parameters.list if list then local targetcharacters = target.characters local done = false for k, v in sortedhash(list) do local tmp = v.template local idx = v.index local dy = v.yoffset or 0 if tmp and idx then local olduni = find(targetcharacters,k,idx) local tmpuni = find(targetcharacters,tmp,idx) local axis = target.mathparameters.AxisHeight if axis then while olduni and tmpuni do local oldchr = targetcharacters[olduni] local tmpchr = targetcharacters[tmpuni] local oldht = oldchr.height or 0 local olddp = oldchr.depth or 0 local tmpht = tmpchr.height or 0 local tmpdp = tmpchr.depth or 0 local scale = (tmpht + tmpdp) / (oldht + olddp) local total = (oldht - olddp) * scale local yoffset = axis - total/2 oldchr.effect = { squeeze = scale } oldchr.height = scale * oldht + yoffset oldchr.depth = scale * olddp - yoffset oldchr.yoffset = yoffset olduni = oldchr.next tmpuni = tmpchr.next end done = registerdone(done,k) end end end feedback_tweak("addvariants",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 done = registerdone(done,t) 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 list = { { 0x2032, 1 }, { 0x2033, 2, 0x2032 }, { 0x2034, 3, 0x2032 }, { 0x2057, 4, 0x2032 }, { 0x2035, 1 }, { 0x2036, 2, 0x2035 }, { 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[2] 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[2] if count > 1 then local unicode = entry[1] local original = entry[3] 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 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 -- done = registerdone(done,u) -- 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 done = registerdone(done,u) end end feedback_tweak("simplifykerns",target,original,done) end end -- In TeX The Program we find in section 543 a remark about the second use of math -- italic: it is always added to the width except with respect to the position of -- the subscript. That paragraph also mentions that the number of different widths -- is normally small so they can be shared (there is an eight bit index into a width -- array). Actually the limits of at most 16 heights and depths has as side effect -- that we get some snapping of sizes. Now, because many math characters have an -- italic correction, the saving on shared widths is negated by the amount of (at -- most 64) italics. So in practice there is no gain and the italic correction could -- have served as bottom kern. We think that the following approach (that we actualy -- came to by a different reasonsing: inconsistent open type fonts) is quite valid -- and robust. It's just that the opentype math fonts should never have gone that -- TeX italic route. Italic usage is more clear from section 543 than from the math -- rendering code. 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 c.bottomanchor = width/2 -- maybe optional end if italic then c.italic = nil okay = true end if okay then done = registerdone(done,u) 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 goto done end if 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 ::done:: 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 done = registerdone(done,u) 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 -- kind of weird ... needs to be in sync with engine .. needs checking c.topanchor = width + half c.bottomanchor = width - half -- print(width/65536,c.topanchor/65536) 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 done = registerdone(done,unicode) 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 -- done = registerdone(done,unicode) -- 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 function mathtweaks.margins(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.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 * width end local k = data.bottom ; if k and k ~= 0 then chardata.bottommargin = k * width end done = registerdone(done,unicode) 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("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 nps = fonts.helpers.newprivateslot local umbracepiece = nps("um brace piece") -- will be created local lmbracepiece = nps("lm brace piece") -- will be created local cmbracepiece = nps("cm brace piece") -- will be created : center piece for brace builder hack local ulbracepiece = nps("ul brace piece") local urbracepiece = nps("ur brace piece") local llbracepiece = nps("ll brace piece") local lrbracepiece = nps("lr brace piece") 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, -- macron (bar) [0x0305] = 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 local newprivateslot = fonts.helpers.newprivateslot -- no checking for present extensibles function mathtweaks.addrules(target,original,parameters) local characters = target.characters local thickness = target.mathparameters.OverbarRuleThickness local width = target.parameters.emwidth / 3 local step = width / 2 local quarter = thickness / 4 local half = thickness / 2 local double = thickness * 2 local done = false characters[0x203E] = { -- middle used for all kind width = width, height = half, depth = half, yoffset = - half, 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 }, }, partsorientation = "horizontal", } local function build(target,leftarrow,rightarrow) if leftarrow and rightarrow then -- actually the same sort of code as we have for antykwa local left = leftarrow.parts local right = rightarrow.parts if left and right then local leftline = right[1].glyph local rightline = left[#left].glyph local leftdata = characters[leftline] local rightdata = characters[rightline] local leftwidth = leftdata.width local rightwidth = rightdata.width local result = characters[target] -- copytable(leftdata) if not result or result.width == 0 then result = { height = leftdata.height, depth = leftdata.depth, width = 0.9*leftwidth + rightwidth, unicode = target, commands = { slotcommand[0][leftline], leftcommand[0.1*leftwidth], slotcommand[0][rightline], }, } characters[target] = result end result.parts = { { advance = leftwidth, glyph = leftline, ["end"] = .9*leftwidth, start = 0 }, { advance = rightwidth, glyph = rightline, ["end"] = .1*leftwidth, start = .9*rightwidth, extender = 1 }, } result.partsorientation = "horizontal" done = registerdone(done,target) end end end build(0x305,characters[0x20D6],characters[0x20D7]) -- overbar accent build(0x332,characters[0x20EE],characters[0x20EF]) -- underbar accent -- -- lucida lacks them ... -- if not characters[0x23B4] then local depth = 0 local height = 5 * thickness local tpiece = addprivate(target,"bracket-piece-top",{ width = thickness, height = height, depth = depth, commands = { upcommand[thickness], { "rule", 4 * thickness, thickness } }, }) local mpiece = addprivate(target,"bracket-piece-top-middle",{ width = width, height = height, depth = depth, commands = { upcommand[4*thickness], { "rule", thickness, width } }, }) characters[0x23B4] = { -- over width = double + width, height = height, depth = depth, unicode = 0x23B4, extensible = false, commands = { slotcommand[0][tpiece], slotcommand[0][mpiece], slotcommand[0][tpiece], }, parts = { { advance = thickness, glyph = tpiece, ["end"] = 0, start = half }, { advance = width, glyph = mpiece, ["end"] = step, start = step, extender = 1 }, { advance = thickness, glyph = tpiece, ["end"] = half, start = 0 }, }, partsorientation = "horizontal", } done = registerdone(done,0x23B4) end if not characters[0x23B5] then local depth = 0 local height = 5 * thickness local bpiece = addprivate(target,"bracket-piece-bottom",{ width = thickness, height = height, depth = depth, yoffset = depth, commands = { { "rule", 4 * thickness, thickness } }, }) local mpiece = addprivate(target,"bracket-piece-bottom-middle",{ width = width, height = height, depth = depth, commands = { { "rule", thickness, width } }, }) characters[0x23B5] = { -- under width = double + width, height = height, depth = depth, unicode = 0x23B5, extensible = false, commands = { slotcommand[0][bpiece], slotcommand[0][mpiece], slotcommand[0][bpiece], }, parts = { { advance = thickness, glyph = bpiece, ["end"] = 0, start = half }, { advance = width, glyph = mpiece, ["end"] = step, start = step, extender = 1 }, { advance = thickness, glyph = bpiece, ["end"] = half, start = 0 }, }, partsorientation = "horizontal", } done = registerdone(done,0x23B5) end -- feedback_tweak("rules",target,original,done) end -- vfmath.builders.extension(target) local rbe = newprivateslot("radical bar extender") local fbe = newprivateslot("fraction bar extender") local frp = { newprivateslot("flat rule left piece"), newprivateslot("flat rule middle piece"), newprivateslot("flat rule right piece"), } local rrp = { newprivateslot("radical rule left piece"), newprivateslot("radical rule middle piece"), newprivateslot("radical rule right piece"), } local mrp = { newprivateslot("minus rule left piece"), newprivateslot("minus rule middle piece"), newprivateslot("minus rule right piece"), } local forceextensible_tag = tex.charactertagcodes.forceextensible local function useminus(target,unicode,characters,parameters,skipfirst,what,tounicode) local minus = characters[0x2212] local parts = minus.parts if parameters == true then parameters = { } end if parts then minus.tag = forceextensible_tag what = copytable(what) parts = copytable(parts) local xscale = parameters.xscale or 1 -- why not applied to width ? local yscale = parameters.yscale or 1 local mwidth = minus.width local mheight = minus.height local height = (parameters.height or 1) * mheight local yshift = (parameters.yoffset or 0) * mheight local loverlap = parameters.leftoverlap or 0 local roverlap = parameters.rightoverlap or 0 local loffset = parameters.leftoffset or 0 local roffset = parameters.rightoffset or 0 if skipfirst then remove(parts,1) remove(what,1) end height = height / 2 yshift = yshift + height for i=1,#parts do local part = parts[i] local glyph = part.glyph local gdata = characters[glyph] local width = gdata.width local xshift = 0 if i == 1 and loverlap ~= 0 then xshift = loverlap * width width = width - xshift elseif i == #parts and roverlap ~= 0 then width = width - roverlap * width end characters[what[i]] = { height = height, depth = height, width = width, advance = gdata.width, unicode = 0xFFFD, -- we have no unicode ignore ... maybe zws commands = { leftcommand[xshift], downcommand[yshift], { "slot", 0, glyph, xscale, yscale }, }, } -- we should overlap more ... if part["start"] >= width then part["start"] = width end if part["end"] >= width then part["end"] = width end part.advance = width part.glyph = what[i] end characters[what[1]].unicode = unicode -- nice for tagging xshift = loffset * mwidth + loverlap * mwidth width = mwidth - xshift - roffset * mwidth - roverlap * mwidth characters[unicode] = { -- base character height = height, depth = height, width = width, tag = forceextensible_tag, -- not needed as we force: commands = { leftcommand[xshift], downcommand[yshift], { "slot", 0, 0x2212, xscale, yscale }, }, unicode = tounicode or unicode, -- extensibles parts = parts, partsorientation = "horizontal", } end end -- add minus parts of not there and create clipped clone local function checkminus(target,unicode,characters,parameters,skipfirst,what,tounicode) local minus = characters[unicode] local parts = minus.parts if parameters == true then parameters = { } end local p_normal = 0 local p_flat = 0 local mwidth = minus.width local height = minus.height local depth = minus.depth local loffset = parameters.leftoffset or 0 local roffset = parameters.rightoffset or 0 local lshift = mwidth * loffset local rshift = mwidth * roffset local width = mwidth - lshift - rshift if parts then -- print("minus has parts") if lshift ~= 0 or width ~= mwidth then parts = copytable(parts) for i=1,#parts do local part = parts[i] local glyph = part.glyph local gdata = characters[glyph] local width = gdata.width local advance = part.advance local lshift = 0 if i == 1 and left ~= 0 then lshift = loffset * width width = width - lshift advance = advance - lshift elseif i == #parts and roffset ~= 0 then width = width - rshift advance = advance - rshift end characters[what[i]] = { height = height, depth = depth, width = width, commands = { leftcommand[lshift], slotcommand[0][glyph], -- { "offset", lshift, 0, glyph }, }, } part.glyph = what[i] part.advance = advance end minus.parts = parts minus.partsorientation = "horizontal" end else local f_normal = formatters["M-NORMAL-%H"](unicode) -- local p_normal = hasprivate(main,f_normal) p_normal = addprivate(target,f_normal,{ height = height, width = width, commands = { push, leftcommand[lshift], slotcommand[0][unicode], pop, -- { "offset", lshift, 0, unicode }, }, }) -- local step = width/2 local step = .8*width minus.parts = { { extender = 0, glyph = p_normal, ["end"] = step, start = 0, advance = width }, { extender = 1, glyph = p_normal, ["end"] = step, start = step, advance = width }, { extender = 0, glyph = p_normal, ["end"] = 0, start = step, advance = width }, } minus.partsorientation = "horizontal" end minus.unicode = tounicode or unicode end function mathtweaks.replacerules(target,original,parameters) local characters = target.characters local minus = parameters.minus local fraction = parameters.fraction local radical = parameters.radical local stacker = parameters.stacker if minus then checkminus(target,0x2212,characters,minus,false,mrp) end if fraction then useminus(target,fbe,characters,fraction,false,frp,0x2044) -- division slash end if radical then if not characters[rbe] then local skipfirst = true if radical.skipfirst == false then -- explicit skipfirst = false end useminus(target,rbe,characters,radical,skipfirst,rrp,0x2061) -- apply function end end if stacker then useminus(target,0x203E,characters,stacker,false,frp) 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 = 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 -- chardata.parts = { { glyph = unicode } } 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 chardata.partsorientation = "horizontal" 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 -- [0x2212] = { { 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 [0x21C6] = centered, -- leftoverrightarrow [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 -- we should follow the next chain first 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 nps = fonts.helpers.newprivateslot local radicalbarextender = nps("radical bar extender") -- we reserve it here 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 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 if data.rorrim then -- the original does the magic else data.height = (data.height or 0) + (data.depth or 0) data.yoffset = data.depth data.depth = 0 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 local next = data.next if next then fix(target,original,characters,next) else -- -- assume no depth -- local parts = data.parts -- if parts then -- fix(target,original,characters,parts[1].glyph) -- end 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.innerlocation = l.location == "right" and "right" or "left" 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 local function fix(target,original,characters,u,l) local data = characters[u] if data then data.leftmargin = (l.left or 1) * (data.width or 0) data.rightmargin = (l.right or 1) * (data.width or 0) end end function mathtweaks.radicalbodymargins(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, } done = registerdone(done,unicode) 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 -- done = registerdone(done,normal) 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 }, [0x0308] = { 0x00A8, false }, [0x0304] = { 0x00AF, false }, -- 305 [0x0301] = { 0x00B4, false }, [0x0302] = { 0x02C6, true }, [0x030C] = { 0x02C7, true }, [0x0306] = { 0x02D8, false }, [0x0307] = { 0x02D9, false }, [0x030A] = { 0x02DA, false }, [0x0303] = { 0x02DC, true }, [0x20DB] = { 0x20DB, false }, -- [0x20EF] = { 0x20EF, false }, } datasets.fixaccents = mapping datasets.extendaccents = mapping datasets.flattenaccents = mapping datasets.copyaccents = mapping -- local flat = stretchingdata.flataccent -- if flat then -- -- Nasty! xoffset needed. Check this when we patch vf. -- local flatdata = characters[flat] -- flatdata.width = width -- flatdata.advance = 0 -- flatdata.topanchor = topanchor -- flatdata.xoffset = width + topanchor -- end local cdata = characters.data 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 if false then local b = target.descriptions[stretching].boundingbox if b then local llx = b[1] * target.parameters.hfactor local urx = b[3] * target.parameters.hfactor width = urx - llx stretchingdata.width = width stretchingdata.xoffset = - llx stretchingdata.advance = urx stretchingdata.topanchor = width/2 stretchingdata.bottomanchor = width/2 end else 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] } end done = registerdone(done,stretching) end end feedback_tweak("fixaccents",target,original,done) end function mathtweaks.checkaccents(target,original,parameters) local characters = target.characters local done = false local factor = target.parameters.hfactor for unicode, data in sortedhash(characters) do local width = data.width if width == 0 then local d = chardata[data.unicode or unicode] local c = d and d.category if c == "mn" or c == "sk" or c == "lm" then -- we can probably can go local b = target.descriptions[unicode] if b then b = b.boundingbox end if b then local topanchor = data.topanchor or 0 local llx = b[1] * factor local urx = b[3] * factor -- if topanchor < 0 then data.advance = data.width if true then -- width = - topanchor width = 2 * (topanchor - llx) -- data.commands = { -- -- rightcommand[width+width/2], -- rightcommand[-llx], -- slotcommand[0][unicode] -- } data.xoffset = -llx else width = urx - llx data.commands = { leftcommand[llx], slotcommand[0][unicode] } end data.width = width data.topanchor = width/2 data.bottomanchor = width/2 end done = registerdone(done,unicode) end end end feedback_tweak("checkaccents",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 done = registerdone(done,stretching) 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 local f_flat = formatters["flat accent %05X"] function mathtweaks.flattenaccents(target,original,parameters) local characters = target.characters local force = parameters.force local squeeze = parameters.squeeze or 0.85 local ofactor = parameters.offset or (squeeze/8.5) local hfactor = parameters.height or 0.95 -- (1 - ofactor) local done = false for stretching, entry in sortedhash(mapping) do local code = stretching local last = characters[stretching] while last do if force or not last.flataccent then local slot = nps(f_flat(code)) local height = last.height or 0 -- print(last.width/65536,code,slot) -- data.effect = { squeeze = squeeze } characters[slot] = { width = last.width, depth = last.depth, height = last.height * hfactor, topanchor = last.topanchor, bottomanchor = last.bottomanchor, commands = { { "offset", 0, ofactor * height, code, 1, squeeze } }, -- commands = { slotcommand[0][code] }, -- effect = { squeeze = squeeze }, -- next = last.next, unicode = last.unicode, } -- if code == 770 then -- inspect(last) -- end last.flataccent = slot done = registerdone(done,stretching) end code = last.next if code then last = characters[code] 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] }, commands = stretchingdata.commands or { charcommand[stretching] }, topanchor = stretchingdata.topanchor, -- unicode = stretching, -- when we alias to combiners unicode = alias, -- when we keep the original } done = registerdone(done,stretching) end end end feedback_tweak("copyaccents",target,original,done) end function mathtweaks.keepbases(target,original,parameters) local characters = target.characters local done = false local list = parameters.list if list == "default" then list = sortedkeys(mapping) end if list and #list > 0 then for i=1,#list do -- assumes sorting local unicode = list[i] local chardata = characters[unicode] if chardata then chardata.keepbase = true done = registerdone(done,unicode) end end else -- maybe also hash end feedback_tweak("keepbases",target,original,done) end end do function mathtweaks.inspect(target,original,parameters) local characters = target.characters local slot = parameters.slot or parameters.unicode if slot then -- todo: show unicode report_math(formatters["%C data:"](slot)) inspect(characters[slot]) end end end -- do -- -- local single = 0x003D -- local double = 0x2A75 -- local triple = 0x2A76 -- -- function mathtweaks.addequals(target,original,parameters) -- local characters = target.characters -- local basechar = characters[single] -- local width = basechar.width -- local height = basechar.height -- local depth = basechar.depth -- local advance = (parameters.advance or 1/20) * width -- local char = charcommand[single] -- local left = leftcommand[advance] -- characters[double] = { -- unicode = double, -- width = 2*width - 1*advance, -- height = height, -- depth = depth, -- commands = { char, left, char }, -- } -- characters[triple] = { -- unicode = triple, -- width = 3*width - 2*advance, -- height = height, -- depth = depth, -- 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 local function jointwo(characters,force,unicode,ds,u1,d12,u2) if force or not characters[unicode] then local c1 = characters[u1] local c2 = characters[u2] if c1 and c2 then local w1 = c1.width local w2 = c2.width local width if d12 == false then d12 = 0 width = w2 elseif d12 < 0 then d12 = d12 * w2 width = w2 else d12 = d12 * ds width = w1 + w2 - d12 end characters[unicode] = { unicode = unicode, width = width, height = max(c1.height or 0, c2.height or 0), depth = max(c1.depth or 0, c2.depth or 0), keepvirtual = true, commands = { -- { "inspect" }, -- { "trace" }, slotcommand[0][u1], -- { "trace" }, d12 ~= 0 and leftcommand[d12] or false, slotcommand[0][u2], -- { "trace" }, }, } end end end local function jointhree(characters,force,unicode,ds,u1,d12,u2,d23,u3) if force or not characters[unicode] then local c1 = characters[u1] local c2 = characters[u2] local c3 = characters[u3] if c1 and c2 and c3 then local w1 = c1.width local w2 = c2.width local w3 = c3.width d12 = d12 * ds d23 = d23 * ds characters[unicode] = { unicode = unicode, width = w1 + w2 + w3 - d12 - d23, height = max(c1.height or 0, c2.height or 0, c3.height or 0), depth = max(c1.depth or 0, c2.depth or 0, c3.depth or 0), commands = { slotcommand[0][u1], d12 ~= 0 and leftcommand[d12] or false, slotcommand[0][u2], d23 ~= 0 and leftcommand[d23] or false, slotcommand[0][u3], } } end end end function mathtweaks.addequals(target,original,parameters) local characters = target.characters local step = target.parameters.size/18 local force = parameters.force force = true jointwo (characters,force,0x2254,step,0x03A,0,0x03D) -- := jointhree(characters,force,0x2A74,step,0x03A,0,0x03A,0,0x03D) -- ::= jointwo (characters,force,0x2A75,step,0x03D,0,0x03D) -- == jointhree(characters,force,0x2A76,step,0x03D,0,0x03D,0,0x03D) -- === 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 actuarianrightlong = nps("delimited right annuity long") -- 0x020E7 local actuarianrightshort = nps("delimited right annuity short") local actuarianleftlong = nps("delimited left annuity long" ) local actuarianleftshort = nps("delimited left annuity short" ) local placehold = nps("delimited ghost annuity") local actuarianbottomrightlong = nps("delimited bottom right annuity long") -- 0x020E7 local actuarianbottomrightshort = nps("delimited bottom right annuity short") local actuarianbottomleftlong = nps("delimited bottom left annuity long" ) local actuarianbottomleftshort = nps("delimited bottom left annuity short" ) 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[placehold] = { width = 2*linewidth, height = baseheight, depth = basedepth, unicode = actuarian, -- whatever callback = "devirtualize", commands = { rightcommand[linewidth], downcommand[basedepth], { "rule", basetotal, 0 }, }, } -- 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[actuarianrightlong] = { width = 2*linewidth, height = baseheight, depth = basedepth, unicode = actuarian, callback = "devirtualize", commands = { downcommand[basedepth], { "rule", basetotal, linewidth }, }, parts = { { advance = basetotal, ["end"] = used, glyph = actuarianrightlong, start = 0, }, { advance = basetotal, ["end"] = 0, extender = 1, glyph = actuarianrightlong, start = used, }, } } characters[actuarianbottomrightlong] = characters[actuarianrightlong] characters[actuarianrightshort] = { width = 2*linewidth, height = baseheight, depth = basedepth, unicode = actuarian, callback = "devirtualize", commands = { upcommand[baseheight-4*linewidth], { "rule", 4*linewidth, linewidth }, }, parts = { { advance = basetotal, ["end"] = used, glyph = actuarianrightshort, start = 0, }, { advance = basetotal, ["end"] = 0, extender = 1, glyph = actuarianrightshort, start = used, }, } } characters[actuarianbottomrightshort] = { width = 2*linewidth, height = linewidth, depth = basedepth, unicode = actuarian, callback = "devirtualize", commands = { downcommand[basedepth], { "rule", 4*linewidth, linewidth }, }, parts = { { advance = basetotal, ["end"] = used, glyph = actuarianrightshort, start = 0, }, { advance = basetotal, ["end"] = 0, extender = 1, glyph = actuarianrightshort, start = used, }, } } characters[actuarianleftlong] = { width = 2*linewidth, height = baseheight, depth = basedepth, unicode = actuarian, -- whatever callback = "devirtualize", commands = { rightcommand[linewidth], downcommand[basedepth], { "rule", basetotal, linewidth }, }, parts = { { advance = basetotal, ["end"] = used, extender = 1, glyph = placehold, start = 0, }, { advance = basetotal, ["end"] = 0, glyph = actuarianleftlong, start = used, }, } } characters[actuarianbottomleftlong] = characters[actuarianleftlong] characters[actuarianleftshort] = { width = 2*linewidth, height = baseheight, depth = basedepth, unicode = actuarian, -- whatever callback = "devirtualize", 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 = actuarianleftshort, start = used, }, } } characters[actuarianbottomleftshort] = { width = 2*linewidth, -- height = baseheight, height = linewidth, depth = basedepth, unicode = actuarian, -- whatever callback = "devirtualize", commands = { rightcommand[linewidth], downcommand[basedepth], { "rule", 4*linewidth, linewidth }, }, parts = { { advance = basetotal, ["end"] = used, extender = 1, glyph = placehold, start = 0, }, { advance = basetotal, ["end"] = 0, glyph = actuarianleftshort, 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") }, { 0x2032, nps("delimited right prime"), nps("delimited ghost prime"), false, 1 }, { 0x2033, nps("delimited right dprime"), nps("delimited ghost dprime"), false, 1 }, { 0x2034, nps("delimited right tprime"), nps("delimited ghost tprime"), false, 1 }, { 0x2057, nps("delimited right qprime"), nps("delimited ghost qprime"), false, 1 }, { 0x2035, nps("delimited right rprime"), nps("delimited ghost rprime"), false, 1 }, { 0x2036, nps("delimited right drprime"), nps("delimited ghost rdprime"), false, 1 }, { 0x2037, nps("delimited right dtprime"), nps("delimited ghost rtprime"), false, 1 }, { 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 }, -- If needed we can have an installer: { 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") }, { 0x2020, nps("delimited right dagger 1"), nps("delimited ghost dagger 1"), false, 1 }, { 0x2021, nps("delimited right ddagger 1"), nps("delimited ghost ddagger 1"), false, 1 }, { 0x2217, nps("delimited right ast 1"), nps("delimited ghost ast 1"), false, 1 }, { 0x22C6, nps("delimited right star 1"), nps("delimited ghost star 1"), false, 1 }, } 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 size = entry[5] or 0 local basechar = characters[basecode] local compactscale = 1 -- bah ... if basechar and target.properties.compactmath and size > 0 then compactscale = target.parameters[size > 1 and "scriptscriptscale" or "scriptscale"] / 1000 for i=1,size do basecode = basechar.smaller or basecode basechar = characters[basecode] end end if basechar then local scale = (parameters.scale or 1) * compactscale 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 = 0xFFFD, commands = { downcommand[used], { "rule", used, 0 }, }, } local parts = { { advance = used, ["end"] = used, extender = 1, glyph = movecode, -- bottom start = used, }, { advance = total, ["end"] = 0, glyph = fouriercode, -- top start = total, }, } if reverse then parts[1], parts[2] = parts[2], parts[1] end characters[fouriercode] = { width = basewidth, height = baseheight, -- somehow no \primed antykwa (unless we double the height) depth = basedepth, unicode = basecode, commands = { scale == 1 and charcommand[basecode] or { "slot", 0, basecode, scale, scale }, }, partsorientation = "vertical", parts = parts, -- keepvirtual = basechar.commands and true or false, -- callback = "devirtualize", } 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 variantlist(unicode,chardata,total,used) chardata.varianttemplate = 0x0028 -- chardata.next = nil -- better use wipe variants but for testign we keep them chardata.parts = { { advance = total, ["end"] = used, glyph = unicode, start = 0, }, { advance = total, -- ["end"] = 0, -- ["end"] = used/5, -- prevents small gap with inward curved endpoints ["end"] = 4*used/5, -- is this better? extender = 1, glyph = unicode, start = used, }, } chardata.partsorientation = "vertical" end -- This is such an inconsistent mess that we cannot simply copy variants so we -- just accept some middle ground for the missing ones. See goodie files for when -- 'check' and/or 'variant' is used. function mathtweaks.addbars(target,original,parameters) local characters = target.characters local template = single local basechar = characters[template] local tempchar = basechar local check = parameters.check -- local variant = false -- alas ... too large: tonumber(parameters.variant) -- if variant then -- for i=1,variant do -- local n = tempchar.next -- if n then -- template = n -- tempchar = characters[template] -- else -- break -- end -- end -- end local width = tempchar.width local height = tempchar.height local depth = tempchar.depth local advance = (parameters.advance or 1/10) * width local used = 1.2*height -- large overlap because no smaller pieces local total = height + depth -- if not check or not basechar.parts then variantlist(template,basechar,total,used) end if not check or not characters[double] then basechar = { unicode = double, width = 2*width - 1*advance, height = height, depth = depth, -- callback = "devirtualize", commands = { charcommand[template], leftcommand[advance], charcommand[template], }, } characters[double] = basechar else basechar = characters[double] end if not check or not basechar.parts then variantlist(double,basechar,total,used) end if not check or not characters[triple] then basechar = { unicode = triple, width = 3*width - 2*advance, height = height, depth = depth, -- callback = "devirtualize", commands = { charcommand[template], leftcommand[advance], charcommand[template], leftcommand[advance], charcommand[template], }, } characters[triple] = basechar else basechar = characters[triple] end if not check or not basechar.parts then variantlist(triple,basechar,total,used) end -- if trace_tweaking then report_tweak("single, double and triple bars added",target,original) 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 function mathtweaks.sortvariants(target,original,parameters) local list = parameters.list if list then local characters = target.characters local horizontal = parameters.orientation == "horizontal" for i=1,#list do local u = list[i] local c = characters[u] if c then local t = { } while true do local n = c.next if n then c = characters[n] end if c and not c.parts then if horizontal then t[c.width or 0] = n else t[(c.height or 0) + (c.depth or 0)] = n end else break end end local c = characters[u] for k, v in sortedhash(t) do c.next = v c = characters[v] end end 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 if next == unicode then report_tweak("skipping cyclic %U (%s)",target,original,unicode,"next") else add(target,original,characters,next,"next") end 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 0x%08X",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) done = registerdone(done,r) end end end end end feedback_tweak("setovershoots",target,original,done) end end -- there is no real need for this 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 local theunicode = entry.unicode local unicodes = entry.unicodes local keep = (entry.keep == true) or (parameters.keep == true) if (thesource and thetarget) or unicodes then local function getrange(t) return (type(t) == "table" and t) or (type(t) == "string" and blocks[t]) -- .gaps end local sourcerange = thesource and getrange(thesource) local targetrange = thetarget and getrange(thetarget) local unicoderange = theunicode and getrange(theunicode) local firsttarget = targetrange and targetrange.first local firstsource = sourcerange and sourcerange.first local lastsource = sourcerange and sourcerange.last or firstsource if (firstsource and firsttarget) or unicodes then local offset = unicodes and 0 or (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, } if id <= 0 then report_math("font %a doesn't exist",fullname) else local chars = fontchars[id] local dropin = fontdata[id] local index = false for i=1,#fonts do local f = fonts[i] if f.id == id and f.size == size then index = i break end end if not index then index = #fonts + 1 fonts[index] = { id = id, size = size } end -- copy characters local function use(sourceunicode,targetunicode) if keep and characters[targetunicode] then -- okay else if feature then sourceunicode = getsubstitution(dropin,sourceunicode,feature,true,"math","dflt") or sourceunicode end characters[targetunicode] = copiedglyph(target,characters,chars,sourceunicode,index) end end if unicodes then for i=1,#unicodes do local unicode = unicodes[i] if chars[unicode] then use(unicode,unicode) end end else for s=firstsource,lastsource do local t = s + offset local sourceunicode = mathgaps[s] or s if chars[sourceunicode] then use(sourceunicode,mathgaps[t] or t) end end end -- local inherit = entry.inherit if inherit then local mathparameters = target.mathparameters local dropparameters = fontdata[id].mathparameters if dropparameters then for name in sortedhash(inherit) do local value = dropparameters[name] if value then mathparameters[name] = value end end end end end elseif feature then local function use(sourceunicode,targetunicode) local variant = getsubstitution(original,sourceunicode,feature,true,"math","dflt") local data = characters[variant] if data then characters[targetunicode] = copytable(data) end end if unicodes then for i=1,#unicodes do local unicode = unicodes[i] use(unicode,unicode) end else for s=firstsource,lastsource do local t = s + offset use(mathgaps[s] or s,mathgaps[t] or t) end end else -- this is a hack for stix providing alphabets without proper unicodes if unicodes then -- not supported else local firstcode = unicoderange and unicoderange.first local descriptions = original.descriptions 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 firstcode then local d = descriptions[sourceunicode] if d then d.unicode = firstcode -- wins end data.unicode = firstcode firstcode = firstcode + 1 end characters[targetunicode] = copytable(data) end end end end 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 tweaklist(target,original,tweaks) if type(tweaks) == "table" then for i=1,#tweaks do local tweak = tweaks[i] 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 function mathtweaks.tweaks(target,original,parameters) tweaklist(target,original,parameters.list) end local function applytweaks(when,target,original) if apply_tweaks then local goodies = original.goodies if goodies then local tweaked = target.tweaked or { } if tweaked[when] then if trace_defining then report_math("tweaking math of %a @ %p (%s: %s)",target.properties.fullname,target.parameters.size,when,"done") end else for i=1,#goodies do local goodie = goodies[i] local mathematics = goodie.mathematics local tweaks = mathematics and mathematics.tweaks if type(tweaks) == "table" then statistics.starttiming(mathtweaks) applied_tweaks = applied_tweaks + 1 tweaks = tweaks[when] if trace_defining then report_math("tweaking math of %a @ %p (%s: %s)",target.properties.fullname,target.parameters.size,when,"okay") end tweaklist(target,original,tweaks) 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) target.properties.hasitalics = false 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 ::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 = "2 integers", actions = { extensiblecode, context } } interfaces.implement { -- can be public with two times "integerargument" name = "horizontalcode", arguments = "2 integers", 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 = "2 integers", actions = { mathematics.variantcode, context }, } interfaces.implement { name = "mathvariantcount", public = true, arguments = "integer", actions = { mathematics.variantcount, context }, }