diff options
Diffstat (limited to 'Master/texmf-dist/tex/context/base/mkxl/math-act.lmt')
-rw-r--r-- | Master/texmf-dist/tex/context/base/mkxl/math-act.lmt | 3929 |
1 files changed, 3701 insertions, 228 deletions
diff --git a/Master/texmf-dist/tex/context/base/mkxl/math-act.lmt b/Master/texmf-dist/tex/context/base/mkxl/math-act.lmt index 9da79976484..370415c8185 100644 --- a/Master/texmf-dist/tex/context/base/mkxl/math-act.lmt +++ b/Master/texmf-dist/tex/context/base/mkxl/math-act.lmt @@ -9,24 +9,46 @@ if not modules then modules = { } end modules ['math-act'] = { -- Here we tweak some font properties (if needed). The commented sections -- have been removed (no longer viable) but can be found in the .lua variant. -local type, next = type, next -local fastcopy, insert, remove = table.fastcopy, table.insert, table.remove +-- 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 setmetatableindex, sortedkeys, sortedhash = table.setmetatableindex, table.sortedkeys, table.sortedhash +local lpegmatch = lpeg.match local trace_defining = false trackers.register("math.defining", function(v) trace_defining = v end) local trace_collecting = false trackers.register("math.collecting", function(v) trace_collecting = v end) +local trace_tweaking = false trackers.register("math.tweaks", function(v) trace_tweaking = v end) local report_math = logs.reporter("mathematics","initializing") +local report_mathtweak = logs.reporter("mathematics","tweak") + +local getfontoffamily = tex.getfontoffamily +local texget = tex.get +local fontcharacters = fonts.hashes.characters +local chardata = characters.data +local extensibles = mathematics.extensibles local context = context local commands = commands local mathematics = mathematics local texsetdimen = tex.setdimen +local texisdimen = tex.isdimen local abs = math.abs +local blocks = characters.blocks +local stepper = utilities.parsers.stepper + local helpers = fonts.helpers local upcommand = helpers.commands.up +local downcommand = helpers.commands.down local rightcommand = helpers.commands.right +local leftcommand = helpers.commands.left local charcommand = helpers.commands.char local prependcommands = helpers.prependcommands @@ -37,6 +59,14 @@ local appendaction = sequencers.appendaction local fontchars = fonts.hashes.characters local fontproperties = fonts.hashes.properties +local mathgaps = mathematics.gaps + +local d_scratchleftoffset = texisdimen("scratchleftoffset") +local d_scratchrightoffset = texisdimen("scratchrightoffset") + +local use_math_goodies = true directives.register("math.nogoodies", function(v) use_math_goodies = not v end) +local checkitalics = false trackers .register("math.checkitalics", function(v) checkitalics = v end) + local mathfontparameteractions = sequencers.new { name = "mathparameters", arguments = "target,original", @@ -46,20 +76,56 @@ appendgroup("mathparameters","before") -- user appendgroup("mathparameters","system") -- private appendgroup("mathparameters","after" ) -- user -function fonts.constructors.assignmathparameters(original,target) +function fonts.constructors.assignmathparameters(original,target) -- wrong way around local runner = mathfontparameteractions.runner if runner then runner(original,target) end end -function mathematics.initializeparameters(target,original) +-- we need a better reset because the following will scale + +local undefined <const> = 0x3FFFFFFF -- maxdimen or undefined_math_parameter + +function mathematics.initializeparameters(target,original,nodimensions) local mathparameters = original.mathparameters if mathparameters and next(mathparameters) then - mathparameters = mathematics.dimensions(mathparameters) - if not mathparameters.SpaceBeforeScript then - mathparameters.SpaceBeforeScript = mathparameters.SpaceAfterScript + 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.RadicalKernAfterExtensible then mathparameters.RadicalKernAfterExtensible = 0 end + if not mathparameters.RadicalKernBeforeExtensible then mathparameters.RadicalKernBeforeExtensible = 0 end + -- target.mathparameters = mathparameters end end @@ -74,182 +140,3651 @@ local how = { RadicalDegreeBottomRaisePercent = "unscaled", NoLimitSupFactor = "unscaled", NoLimitSubFactor = "unscaled", + PrimeRaisePercent = "unscaled", + PrimeRaiseComposedPercent = "unscaled", + PrimeWidthPercent = "unscaled", + AccentTopOvershoot = "unscaled", + AccentBottomOvershoot = "unscaled", + AccentSuperscriptPercent = "unscaled", + DelimiterPercent = "unscaled", + -- + RadicalRuleThickness = "vertical", + OverbarRuleThickness = "vertical", + FractionRuleThickness = "vertical", + UnderbarRuleThickness = "vertical", } +local function scaleparameters(mathparameters,parameters) + if mathparameters and next(mathparameters) and parameters then + local factor = parameters.factor + local hfactor = parameters.hfactor + local vfactor = parameters.vfactor + for name, value in next, mathparameters do + local h = how[name] + if h == "unscaled" then + -- kept + elseif h == "horizontal" then + value = value * hfactor + elseif h == "vertical" then + value = value * vfactor + else + value = value * factor + end + mathparameters[name] = value + end + end +end + function mathematics.scaleparameters(target,original) if not target.properties.math_is_scaled then + scaleparameters(target.mathparameters,target.parameters) + target.properties.math_is_scaled = true + end +end + +-- AccentBaseHeight vs FlattenedAccentBaseHeight + +-- function mathematics.checkaccentbaseheight(target,original) +-- local mathparameters = target.mathparameters +-- if mathparameters and mathparameters.AccentBaseHeight == 0 then +-- if trace_defining then +-- report_math("zero AccentBaseHeight corrected %a @ %p",target.properties.fullname,target.parameters.size) +-- end +-- mathparameters.AccentBaseHeight = target.parameters.xheight -- needs checking +-- end +-- end + +function mathematics.overloadparameters(target,original) + if use_math_goodies then local mathparameters = target.mathparameters if mathparameters and next(mathparameters) then - local parameters = target.parameters - local factor = parameters.factor - local hfactor = parameters.hfactor - local vfactor = parameters.vfactor - for name, value in next, mathparameters do - local h = how[name] - if h == "unscaled" then - -- kept - elseif h == "horizontal" then - value = value * hfactor - elseif h == "vertical"then - value = value * vfactor - else - value = value * factor + 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 - mathparameters[name] = value end end - target.properties.math_is_scaled = true end end --- AccentBaseHeight vs FlattenedAccentBaseHeight +-- a couple of predefined tweaks: -function mathematics.checkaccentbaseheight(target,original) - local mathparameters = target.mathparameters - if mathparameters and mathparameters.AccentBaseHeight == 0 then - mathparameters.AccentBaseHeight = target.parameters.x_height -- needs checking +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 -function mathematics.checkprivateparameters(target,original) - local mathparameters = target.mathparameters - if mathparameters then +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 - local properties = target.properties if parameters then - local size = parameters.size - if size then - if not mathparameters.FractionDelimiterSize then - mathparameters.FractionDelimiterSize = 1.01 * size + report_mathtweak( + "%a, size %P, math size %i, %s", + metadata and metadata.fontname or "unknown", + parameters.size or 655360, + parameters.mathsize or 1, + string.formatters[fmt](...) + ) + else + print("something is wrong") + end + else + report_mathtweak("") + end +end + +local function feedback_tweak(tweak,target,original,done) + if not done or (type(done) == "table" and not next(done)) then +if trace_tweaking then -- for now + report_tweak("no need for %a",target,original,tweak) +end + elseif trace_tweaking then + report_tweak("tweak %a applied to: %s",target,original,tweak,unicodecharlist(done)) + end +end + +mathtweaks.subsets = { + acenorsuvxz = { 0x1D44E, 0x1D450, 0x1D452, 0x1D45B, 0x1D45C, 0x1D45F, 0x1D460, 0x1D462, 0x1D463, 0x1D465, 0x1D467 }, + bhklt = { 0x1D44F, 0x1D455, 0x1D458, 0x1D459, 0x1D461 }, + d = { 0x1D451 }, + f = { 0x1D453 }, + gjqy = { 0x1D454, 0x1D457, 0x1D45E, 0x1D466 }, + i = { 0x1D456 }, + mw = { 0x1D45A, 0x1D464 }, + p = { 0x1D45D }, + dotless = { 0x00049, 0x0004A, 0x00131, 0x00237, 0x1D6A4, 0x1D6A5 }, + integrals = { 0x0222B, 0x0222C, 0x0222D, 0x0222E, 0x0222F, 0x02230, 0x02231, 0x02232, 0x02233, 0x02A0B, 0x02A0C, 0x02A0D, 0x02A0E, 0x02A0F, 0x02A10, 0x02A11, 0x02A12, 0x02A13, 0x02A14, 0x02A15, 0x02A16, 0x02A17, 0x02A18, 0x02A19, 0x02A1A, 0x02A1B, 0x02A1C, 0x02320, 0x02321 }, + horizontalfences = { 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(".") + + detail = function(characters,k) + if type(k) == "string" then + local t = lpegmatch(splitter,k) + local n = #t + if n > 0 then + local base = tonumber(t[1]) or tonumber(t[1],16) + if base then + local c = characters[base] + if c and n > 1 then + local list = t[2] + if list == "parts" then + local nxt = c.next + while nxt do + c = characters[nxt] + nxt = c.next + end + c = c.parts + if c then + local index = t[3] + if index == "*" then + return t + else + if index == "top" then + index = #c + elseif index == "bottom" then + index = 1 + else + index = tonumber(index) + end + if index then + c = c[index] + if c then + return c.glyph + end + end + end + end + elseif list == "variants" then + local index = t[3] + if index == "*" then + local t = { } + local nxt = c.next + while nxt do + t[#t+1] = nxt + c = characters[nxt] + nxt = c.next + end + return t + else + index = tonumber(index) + if index then + local nxt = c.next + while nxt and index > 1 do + c = characters[nxt] + nxt = c.next + index = index - 1 + end + return nxt + end + end + end + end + end + end + else + return k + end + end + +end + +-- This temporary tweak was used when we (MS & HH) were fixing the Latin Modern +-- parameters that relate to script placement. We started from the original cmr +-- ratios combined with the formal specification and ended up with the following +-- values. In the end we rejected this tweak and setteled for checking and fixing: +-- +-- SubscriptShiftDown +-- SubscriptShiftDownWithSuperscript +-- SuperscriptShiftUp +-- SuperscriptShiftUpCramped +-- +-- because it looks a bit arbitrary what values are set. We keep the code below +-- as documentation. + +-- do +-- -- modern +-- -- +-- -- local factors = { +-- -- scripts = { +-- -- SubscriptBaselineDropMin = 0.116, +-- -- SubscriptShiftDown = 0.348, +-- -- SubscriptShiftDownWithSuperscript = 0.573, +-- -- SubscriptTopMax = 0.800, +-- -- SuperscriptBaselineDropMax = 0.896, +-- -- SuperscriptBottomMaxWithSubscript = 0.800, +-- -- SuperscriptBottomMin = 0.250, +-- -- SuperscriptShiftUp = 0.958, +-- -- SuperscriptShiftUpCramped = 0.958, +-- -- } +-- -- } +-- +-- -- -- cambria +-- -- +-- -- local factors = { +-- -- scripts = { +-- -- SubscriptBaselineDropMin = 0.279, +-- -- SubscriptShiftDown = 0.364, +-- -- SubscriptShiftDownWithSuperscript = 0.547, +-- -- SubscriptTopMax = 0.662, +-- -- SuperscriptBaselineDropMax = 0.401, +-- -- SuperscriptBottomMaxWithSubscript = 0.667, +-- -- SuperscriptBottomMin = 0.208, +-- -- SuperscriptShiftUp = 0.654, +-- -- SuperscriptShiftUpCramped = 0.654, +-- -- } +-- -- } +-- +-- -- after some tests and inspection +-- -- +-- +-- local factors = { +-- scripts = { +-- SubscriptBaselineDropMin = 0.100, -- harmless but small (seldom triggered) +-- SubscriptShiftDown = 0.400, -- by inspection in several files +-- SubscriptShiftDownWithSuperscript = 0.400, -- as above +-- SubscriptTopMax = 0.800, -- Microsoft recommendation +-- SuperscriptBaselineDropMax = 0.100, -- see SubscriptBaselineDropMin +-- SuperscriptBottomMaxWithSubscript = 0.800, -- Microsoft recommendation +-- SuperscriptBottomMin = 0.250, -- Microsoft recommendation +-- SuperscriptShiftUp = 0.650, -- by inspection, but also a bit gamble +-- SuperscriptShiftUpCramped = 0.650, -- see above, non-TeX +-- } +-- } +-- +-- datasets.fixparameters = factors +-- +-- function mathtweaks.fixparameters(target,original,parameters) +-- local mathparameters = target.mathparameters +-- if mathparameters and next(mathparameters) then +-- local xheight = target.parameters.xheight +-- -- todo : options +-- for k, v in next, factors.scripts do +-- mathparameters[k] = v * xheight +-- end +-- end +-- end +-- +-- end + +do + + local stepper = utilities.parsers.stepper + local count = 0 + local toeffect = fonts.toeffect + local privateslot = fonts.helpers.privateslot + + local function adapt(list,target,original,targetcharacters,originalcharacters,k,v,compact,n) + k = mathgaps[k] or k + local character = targetcharacters[k] + if character then +-- if not character.tweaked then -- todo: add a force + local t = type(v) + if t == "number" then + v = list[v] + t = type(v) + end + if t == "table" and next(v) then + + local axis = tonumber(v.axis) + if axis then + axis = target.mathparameters.AxisHeight * axis + end + + local factor = v.factor + if factor then + local m = v + v = setmetatableindex({ + width = factor, + height = factor, + depth = factor, + squeeze = factor, + extend = factor, + }, v) + end + local originalslot = v.original + if not originalslot then + local delta = v.delta + if delta then + originalslot = k + delta + end + end + if originalslot then + originalslot = mathgaps[originalslot] or originalslot + local data = targetcharacters[originalslot] + if data then + data = copytable(data) + data.unicode = originalslot + targetcharacters[k] = data + character = data + else + report_mathtweak("no slot %U",originalslot) + return + end + end + -- + local width = character.width + local height = character.height + local depth = character.depth + local italic = character.italic + local topanchor = character.topanchor + local bottomanchor = character.bottomanchor + -- + local widthfactor = v.width + local heightfactor = v.height + local depthfactor = v.depth + local italicfactor = v.italic + local anchorfactor = v.anchor + local advancefactor = v.advance + local xoffsetfactor = v.xoffset + local yoffsetfactor = v.yoffset + local scalefactor = v.scale + local total = (height or 0) + (depth or 0) + if scalefactor ~= 1 then + character.scale = scalefactor + end + if width and width ~= 0 then + if advancefactor then + character.advance = advancefactor * width + else + character.advance = character.advance or width -- so advance is oldwidth + end + if widthfactor then + character.width = widthfactor * width + end + if xoffsetfactor then + character.xoffset = xoffsetfactor * width + end + end + if height and height ~= 0 then + if heightfactor then + character.height = heightfactor * height + end + end + if depth and depthfactor then + character.depth = depthfactor * depth + end + if yoffsetfactor then + character.yoffset = yoffsetfactor * total + end + + if axis then + character.height = (character.height or 0) - axis + character.depth = (character.depth or 0) + axis + character.yoffset = (character.yoffset or 0) + axis + end + + if italicfactor then + if italic then + character.italic = italicfactor * italic + elseif width and italicfactor ~= 1 then + character.italic = italicfactor * width + end + end + if anchorfactor then + character.topanchor = anchorfactor * (topanchor or width) + end +-- if anchorfactor then +-- character.bottomaccent = anchorfactor * (bottomanchor or width) +-- end + -- begin experiment + local line = v.wline + if line then + local parameters = target.parameters + v.line = parameters.hfactor * line / parameters.units + end + -- end experiment + character.effect = toeffect(v) -- todo: move wline test inside here + -- begin experiment + v.line = line + -- end experiment + if trace_tweaking then + report_tweak("adapting dimensions of %U ",target,original,k) + end + -- missing when private + local originaldata = originalcharacters[k] -- or targetcharacters[k] + local smaller = originaldata and originaldata.smaller + if compact and smaller and smaller ~= k then + adapt(list,target,original,targetcharacters,originalcharacters,smaller,v,compact,n+1) + end + count = count + 1 + else + report_mathtweak("invalid dimension entry %U",k) + end +-- character.tweaked = true + if v.all then + local nxt = character.next + if nxt then + adapt(list,target,original,targetcharacters,originalcharacters,nxt,v,compact,n) + else + local parts = character.parts + if parts then + for i=1,#parts do + adapt(list,target,original,targetcharacters,originalcharacters,parts[i],v,compact,n) + end + end + end + end +-- end + else + report_tweak("no character %U",target,original,k) + end + end + + function mathtweaks.dimensions(target,original,parameters) + local list = parameters.list + if list then + local targetcharacters = target.characters + local originalcharacters = original.characters + local compact = target.parameters.textscale and true or false + count = 0 + for k, v in sortedhash(list) do + local t = type(k) + if t == "number" then + adapt(list,target,original,targetcharacters,originalcharacters,k,v,compact,1) + elseif t == "string" then + local d = privateslot(k) or detail(targetcharacters,k) -- watch the private here + local t = type(d) + if t == "table" then + for i=1,#d do + adapt(list,target,original,targetcharacters,originalcharacters,d[i],v,compact,1) + end + elseif t == "number" then + adapt(list,target,original,targetcharacters,originalcharacters,d,v,compact,1) + elseif d then + -- some kind of error + else + local r = blocks[k] + if r then + local done = false + for i=r.first,r.last do + adapt(list,target,original,targetcharacters,originalcharacters,i,v,compact,1) + end + else + stepper(k,function(n) + adapt(list,target,original,targetcharacters,originalcharacters,n,v,compact,1) + end) + end + end + -- elseif t == "table" then + -- for i=1,#t do + -- adapt(list,target,original,targetcharacters,originalcharacters,t[i],v,compact,1) + -- end + end + end + if trace_tweaking and count > 0 then + report_mathtweak("%i dimensions adapted",count) + end + end + end + +end + +do + + function mathtweaks.message(target,original,parameters) + report_mathtweak(parameters.text or "no message") + end + + function mathtweaks.showinfo(target,original,parameters) + local mathparameters = target.mathparameters + for k, v in sortedhash(mathparameters) do + report_mathtweak("%s : %s",k,v) + end + end + +end + + +do + + function mathtweaks.wipevariants(target,original,parameters) + local list = parameters.list + if list then + local targetcharacters = target.characters + -- local originalcharacters = original.characters + local count = 0 + -- local also = getalso(target,original) + local done = false + for k, v in sortedhash(list) do + local ori = targetcharacters[k] + local nxt = ori.next + local cnt = v + if nxt then + local prt = nil + local lst = { } + while nxt do + local chr = targetcharacters[nxt] + lst[#lst+1] = chr + nxt = chr.next + if not nxt then + prt = chr.parts + break + end + end + if prt then + count = count + 1 + if cnt ~= "*" then + if #lst < cnt then + cnt = #lst + end + ori = lst[cnt] + end + ori.parts = prt + end + if not trace_tweaking then + done = true + elseif done then + done[k] = true + else + done = { [k] = true } + end + end + end + feedback_tweak("wipevariants",target,original,done) + end + end + +end + +do + + function mathtweaks.replace(target,original,parameters) + local list = parameters.list + if list then + local targetcharacters = target.characters + local originalcharacters = original.characters + local unicodes = original.resources.unicodes + if unicodes then + local count = 0 + for k, v in sortedhash(list) do + if type(v) == "string" then + v = unicodes[v] + end + if type(v) == "number" then + targetcharacters[mathgaps[k] or k] = targetcharacters[mathgaps[v] or v] + count = count + 1 + end + end + if trace_tweaking and count > 0 then + report_tweak("%i permanent replacements",target,original,count) + end + end + end + end + + function mathtweaks.substitute(target,original,parameters) + local list = parameters.list + if list then + local targetcharacters = target.characters + local originalcharacters = original.characters + local getsubstitution = fonts.handlers.otf.getsubstitution + local count = 0 + for k, v in next, list do -- no need for sortedhash(list) unless we report + local sub = getsubstitution(original,k,v,true) + if sub then + targetcharacters[mathgaps[k] or k] = targetcharacters[mathgaps[sub] or sub] + count = count + 1 + end + end + if trace_tweaking and count > 0 then + report_tweak("%i permanent substitutions",target,original,count) + end + end + end + +end + +do + + -- maybe we'll have a different name + + function mathtweaks.kernpairs(target,original,parameters) + local list = parameters.list + if list then + local targetcharacters = target.characters + local originalcharacters = original.characters + local done = false + + local function add(v,n) + local chardata = targetcharacters[mathgaps[n] or n] + if chardata then + local width = chardata.width + if width then + local kerns = chardata.kerns or { } + for kk, vv in next, v do +-- for kk, vv in sortedhash(v) do + stepper(kk,function(nn) -- todo: also make stepper accept a table + local t = mathgaps[nn] or nn + if t then + kerns[t] = vv * width + if not trace_tweaking then + done = true + elseif done then + done[t] = true + else + done = { [t] = true } + end + end + end) + end + chardata.kerns = kerns + end + end + end + + for k, v in next, list do -- no need for sortedhash(list) unless we report + stepper(k,function(n) -- todo: also make stepper accept a table + add(v,n) + end) + end + +-- for k, v in next, list do -- no need for sortedhash(list) unless we report +-- local chardata = targetcharacters[mathgaps[k] or k] +-- if chardata then +-- local width = chardata.width +-- if width then +-- local kerns = chardata.kerns or { } +-- for kk, vv in next, v do +-- local t = mathgaps[kk] or kk +-- if t then +-- kerns[t] = vv * width +-- count = count + 1 +-- end +-- end +-- chardata.kerns = kerns +-- end +-- end +-- end + + feedback_tweak("kernpairs",target,original,done) + end + end + +end + +do + + local nps = fonts.helpers.newprivateslot + + local list = { + { 0x2032, nps("delimited ghost 0x2032"), 1 }, + { 0x2033, nps("delimited ghost 0x2033"), 2, 0x2032 }, + { 0x2034, nps("delimited ghost 0x2034"), 3, 0x2032 }, + { 0x2057, nps("delimited ghost 0x2057"), 4, 0x2032 }, + { 0x2035, nps("delimited ghost 0x2035"), 1 }, + { 0x2036, nps("delimited ghost 0x2036"), 2, 0x2035 }, + { 0x2037, nps("delimited ghost 0x2037"), 3, 0x2035 }, + } + + datasets.fixprimes = list + + function mathtweaks.fixprimes(target,original,parameters) + local targetcharacters = target.characters + local factor = parameters.factor or 1 + local fake = tonumber(parameters.fake) + for i=1,#list do + local entry = list[i] + local unicode = entry[1] + local count = entry[3] + local used = fonts.handlers.otf.getsubstitution(target,unicode,"ssty",true,"math","dflt") or unicode + local data = targetcharacters[used] + if data then + targetcharacters[unicode] = data + local oldheight = data.height or 0 + local newheight = factor * oldheight + data.yoffset = newheight - (oldheight or 0) + data.height = newheight + data.smaller = nil + elseif not fake then + report_tweak("missing %i prime %U",target,original,count,unicode) + end + end + if fake then + for i=1,#list do + local entry = list[i] + local count = entry[3] + if count > 1 then + local unicode = entry[1] + local original = entry[4] + local data = targetcharacters[original] + if data then + local oldwidth = data.width + local xoffset = fake * oldwidth + local newwidth = oldwidth + (count - 1) * xoffset + targetcharacters[unicode] = { + width = newwidth, + height = data.height, + unicode = unicode, + commands = { + { "offset", 0, 0, original }, + { "offset", xoffset, 0, original }, + count > 2 and { "offset", 2 * xoffset, 0, original } or nil, + count > 3 and { "offset", 3 * xoffset, 0, original } or nil, + }, + } + end + end + end + end + end + + function mathtweaks.addprimed(target,original,parameters) + local characters = target.characters + for i=1,#list do + local entry = list[i] + local basecode = entry[1] + local movecode = entry[2] + local basedata = characters[basecode] + if basedata then + local baseheight = basedata.height or 0 + local basewidth = basedata.width or 0 + local used = baseheight + local total = baseheight + characters[movecode] = { -- todo:share + width = basewidth, + height = used, + unicode = basecode, -- 0xFFFD or space or so + -- callback = "devirtualize", + commands = { + downcommand[used], + { "rule", used, 0 }, + }, + } + basedata.partsorientation = "vertical" + basedata.parts = { + { + advance = used, + ["end"] = used, + extender = 1, + glyph = movecode, + start = used, + }, + { + advance = total, + ["end"] = 0, + glyph = basecode, + start = total, + }, + } + if trace_tweaking then + report_tweak("primed %U added",target,original,basecode) + end + end + end + end + +end + +do + + local nps = fonts.helpers.newprivateslot + + local privates = { + [0x2212] = nps("unary minus"), + [0x002B] = nps("unary plus"), + [0x00B1] = nps("unary plus minus"), + [0x2213] = nps("unary minus plus"), + } + + -- these are the values tested with texgyre-bonum + + local predefined = { + ["unary minus"] = { + original = 0x2212, + extend = .5, + width = .5, + unicode = 0x002D, -- hyphen minus + }, + ["unary plus"] = { + original = 0x002B, + extend = .5, + squeeze = .5, + width = .5, + height = .5, + yoffset = .2, + mode = 2, + wline = .5, + unicode = 0x002B, + }, + ["unary plus minus"] = { + original = 0x00B1, + extend = .5, + squeeze = .5, + width = .5, + height = .5, + yoffset = .2, + mode = 2, + wline = .5, + }, + ["unary minus plus"] = { + original = 0x2213, + extend = .5, + squeeze = .5, + width = .5, + height = .5, + yoffset = .2, + mode = 2, + wline = .5, + }, + } + + -- { + -- tweak = "addprivates", + -- list = { + -- -- for specific parameters see act file + -- ["unary minus"] = { preset = "unary minus" }, + -- ["unary plus"] = { preset = "unary plus" }, + -- ["unary plus minus"] = { preset = "unary plus minus" }, + -- ["unary minus plus"] = { preset = "unary minus plus" }, + -- }, + -- }, + + function mathtweaks.addprivates(target,original,parameters) + local list = parameters.list or predefined + if list then + local targetcharacters = target.characters + local targetparameters = target.parameters + local originalcharacters = original.characters + local processedprivates = { } + for name, v in sortedhash(list) do + if type(v) == "table" then + local preset = v.preset + if preset then + local p = predefined[preset] + if p then + v = table.combine(p,v) + p.preset = nil + else + goto next + end + end + local charslot = v.original + if charslot then + local chardata = targetcharacters[charslot] + if chardata then + local clonedata = copytable(chardata) + local cloneslot = nps(name) + local unicode = v.unicode or clonedata.unicode + clonedata.uncode = unicode + targetcharacters[cloneslot] = clonedata + if trace_tweaking then + report_tweak("cloning %a from %C into %U with tounicode %U",target,original,name,charslot,cloneslot,unicode) + end + end + processedprivates[name] = v + end + ::next:: + end + end + mathtweaks.dimensions(target,original,{ + tweak = parameters.tweak, + list = processedprivates, + }) + end + end + +end + +-- do +-- +-- function mathtweaks.fixanchors(target,original,parameters) +-- local targetcharacters= target.characters +-- local factor = tonumber(parameters.factor) or 0 +-- if factor ~= 0 then +-- local done = false +-- for k, v in next, targetcharacters do +-- local a = v.topanchor +-- if a and a > 0 then +-- v.topanchor = a * factor +-- count = count + 1 +-- if not trace_tweaking then +-- done = true +-- elseif done then +-- done[u] = true +-- else +-- done = { [u] = true } +-- end +-- end +-- end +-- end +-- feedback_tweak("fixanchors",target,original,done) +-- end +-- +-- end + +-- do +-- +-- -- actually this should be a an engine feature driven by category because we don't +-- -- want this in display mode .. only a test for MS and HH +-- +-- local issymbol = characters.is_symbol +-- +-- function mathtweaks.oldstylemath(target,original,parameters) +-- local chardata = characters.data +-- local characters = target.characters +-- local axis = target.mathparameters.AxisHeight +-- local delta = (parameters.factor or .1) * axis +-- target.mathparameters.AxisHeight = (axis - delta) +-- for k, v in sortedhash(characters) do +-- if issymbol[k] then -- quick hack, engine knows +-- v.yoffset = -delta +-- v.height = (v.height or 0) - delta +-- v.depth = (v.depth or 0) - delta +-- end +-- end +-- end +-- +-- function mathtweaks.oldstylemath(target,original,parameters) +-- -- not relevant +-- end +-- +-- end + +do + + function mathtweaks.simplifykerns(target,original,parameters) + local characters = target.characters + local done = false + -- for u, v in sortedhash(characters) do + for u, v in next, characters do + local mathkerns = v.mathkerns + if mathkerns then + local k = mathkerns.topleft + if k then + k = k[#k].kern + if k then + v.topleft = k + end + end + local k = mathkerns.topright + if k then + k = k[#k].kern + if k then + v.topright = k + end + end + local k = mathkerns.bottomleft + if k then + k = k[1].kern -- todo get value at baseline + if k then + v.bottomleft = k + end + end + local k = mathkerns.bottomright + if k then + k = k[1].kern -- todo get value at baseline + if k then + v.bottomright = k + end + end + v.mathkerns = nil + if not trace_tweaking then + done = true + elseif done then + done[u] = true + else + done = { [u] = true } + end + end + end + feedback_tweak("simplifykerns",target,original,done) + end + +end + +do + + local function wipe(whatever,target,original,parameters,field,move,integrals) + local targetcharacters = target.characters + local targetdescriptions = target.descriptions + local factor = target.parameters.factor + local correct = parameters.correct + local done = false + local function getllx(u) + local d = targetdescriptions[u] + if d then + local b = d.boundingbox + if b then + local llx = b[1] + if llx < 0 then + return - llx + end + end + end + return false + end + local function step(s) + while s do + local u = mathgaps[s] or s + local c = targetcharacters[u] + if c then + if field == "topanchor" then + if c.topanchor then + c.topanchor = nil + else + goto smaller + end + else + local okay = false + local italic = c.italic + if move and not c.advance then -- advance check prevents double move + local width = c.width or 0 + c.advance = width + if correct then + local llx = getllx(u) + if llx then + local topanchor = c.topanchor + llx = llx * factor + width = width + llx + c.xoffset = llx + if topanchor then + c.topanchor = topanchor + llx + end + -- too bad (schola e^x): + -- c.bottomleft = (c.bottomleft or 0) - llx + -- c.topleft = (c.topleft or 0) - llx + okay = true + end + end + if italic and italic ~= 0 then + c.width = width + italic + c.bottomright = - italic + okay = true + else + c.width = width + end + end + if italic then + c.italic = nil + okay = true + end + if okay then + if not trace_tweaking then + done = true + elseif done then + done[u] = true + else + done = { [u] = true } + end + else + goto smaller + end + end + goto smaller + ::smaller:: + s = c.smaller + ::variants:: + -- no italics here anyway but we could check them some day + else + break + end + end + end + local list = parameters.list -- todo: ranges + if list == "letters" or parameters.letters then + local chardata = characters.data + -- for k, v in sortedhash(targetcharacters) do + for k, v in next, targetcharacters do + if v.italic then + local d = chardata[v.unicode] + local c = d and d.category + if c == "ll" or c == "lu" then + step(k) + end + end + end + return + elseif not list or list == "all" or list == true or parameters.all then + list = sortedkeys(targetcharacters) + elseif type(list) == "string" then + list = { list } + end + for i=1,#list do + local l = list[i] + local t = type(l) + if not l then + -- can be false + elseif t == "table" then + for i=1,#l do + step(l[i]) + end + elseif t == "number" then + step(l) + else + local r = blocks[l] + if r then + for i=r.first,r.last do + step(i) + end + else + stepper(l,step) + end + end + end + feedback_tweak(whatever,target,original,done) + end + + function mathtweaks.wipeanchors(target,original,parameters) + wipe("wipeanchors",target,original,parameters,"topanchor") + end + + function mathtweaks.wipeitalics(target,original,parameters) + if not checkitalics then + wipe("wipeitalics",target,original,parameters,"italic") + end + end + + function mathtweaks.moveitalics(target,original,parameters) + wipe("moveitalics",target,original,parameters,"italic",true) + end + + -- function mathtweaks.fixdigits(target,original,parameters) + -- mathtweaks.fixanchors(target,original,{ list = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 } }) + -- end + +end + +do + + function mathtweaks.topanchors(target,original,parameters) + local characters = target.characters + local list = parameters.list + if list then + local done = false + for u, v in sortedhash(list) do + local c = characters[k] + if c then + local w = c.width + if w and w ~= 0 then + c.topanchor = v * w + if trace_tweaking then + -- todo + end + if not trace_tweaking then + done = true + elseif done then + done[u] = true + else + done = { [u] = true } + end + end + end + end + feedback_tweak("topanchors",target,original,done) + end + end + + function mathtweaks.movelimits(target,original,parameters) + local characters = target.characters + local list = parameters.list + if list then + local factor = parameters.factor or 1 + local also = getalso(target,original) + local done = { } + local function relocate(u,factor) + if done[u] then + return + end + done[u] = true + local c = characters[u] + if c then + local italic = c.italic + if italic then + if italic ~= 0 then + local width = c.width or 0 + local half = (italic/2) * factor + c.topanchor = width + half + c.bottomanchor = width - half + c.bottomright = - italic * (parameters.icfactor or 1) + if trace_tweaking then + -- todo + end + end + c.italic = nil + end + local s = c.smaller + if s then + relocate(s,factor) + end + local n = c.next + if n then + relocate(n,factor) + end + -- Kind of tricky: we configure the engine to use the vitalic + -- so when we tweak we need to set that to zero. + local parts = c.parts + local italic = c.partsitalic + if parts and italic then + if italic ~= 0 then + local tchar = characters[parts[#parts].glyph] + local bchar = characters[parts[1].glyph] + local width = tchar.width or 0 + local half = (italic/2) * factor + tchar.topanchor = width + half + bchar.bottomanchor = width - half + bchar.bottomright = - italic + if trace_tweaking then + -- todo + end + tchar.italic = nil + bchar.italic = nil + end + c.vitalic = nil + end + if also then + local a = also[u] + if a then + for i=1,#a do + relocate(a[i],factor) + end + end + end + end + end + if #list > 0 then + for i=1,#list do + relocate(list[i],factor) + end + else + for k, v in sortedhash(list) do + relocate(k,tonumber(v) or factor) + end + end + feedback_tweak("movelimits",target,original,done) + end + end + +end + +do + + -- musical timestamp: March 2022, Antonio Sanches (Bad Hombre), live performance in NL + + function mathtweaks.kerns(target,original,parameters) + local kerns = parameters.list + if kerns then + local characters = target.characters + local done = false + local function setone(uc,data) + local function set(unicode) + unicode = mathgaps[unicode] or unicode + local chardata = characters[unicode] + if chardata then + local width = chardata.width or 0 + local k = data.topleft ; if k and k ~= 0 then chardata.topleft = k * width end + local k = data.topright ; if k and k ~= 0 then chardata.topright = k * width end + local k = data.bottomleft ; if k and k ~= 0 then chardata.bottomleft = k * width end + local k = data.bottomright ; if k and k ~= 0 then chardata.bottomright = k * width end + if not trace_tweaking then + done = true + elseif done then + done[unicode] = true + else + done = { [unicode] = true } + end + end + end + local unicode = detail(characters,uc) + if type(unicode) == "table" then + for i=1,#unicode do + set(unicode[i]) + end + elseif unicode then + set(unicode) + end + end + for unicode, data in next, kerns do + setone(unicode,data) -- withscriptcode(tfmdata,unicode,data,kernone) + -- also smaller + end + feedback_tweak("kerns",target,original,done) + end + end + +end + +do + + function mathtweaks.margins(target,original,parameters) + local margins = parameters.list + if margins then + local characters = target.characters + local done = false + local function setone(unicode,data) + unicode = mathgaps[unicode] or unicode + local chardata = characters[unicode] + if chardata then + local width = chardata.width or 0 + local total = (chardata.height or 0) + (chardata.depth or 0) + local k = data.left ; if k and k ~= 0 then chardata.leftmargin = k * width end + local k = data.right ; if k and k ~= 0 then chardata.rightmargin = k * width end + local k = data.top ; if k and k ~= 0 then chardata.topmargin = k * total end + local k = data.bottom ; if k and k ~= 0 then chardata.bottommargin = k * total end + if not trace_tweaking then + done = true + elseif done then + done[unicode] = true + else + done = { [unicode] = true } + end + end + end + for unicode, data in next, margins do + setone(unicode,data) -- withscriptcode(tfmdata,unicode,data,kernone) + -- also smaller + end + feedback_tweak("margins",target,original,done) + end + end + +end + +do + + -- musical timestamp: June 2022, Porcupine Tree - Rats Return + + -- we can actually share these and flag them as being tweaked + + local function scale(t,width,total) + local r = { } + for i=1,#t do + local ti = t[i] + local kern = ti.kern + local height = ti.height + if kern then + kern = width * kern + end + if height then + height = total * height + end + r[i] = { + kern = kern or 0, + height = height or 0, + } + end + return r + end + + function mathtweaks.staircase(target,original,parameters) + local kerns = parameters.list + if kerns then + local characters = target.characters + local function kernone(unicode,data) + local chardata = characters[mathgaps[unicode] or unicode] + local total = (chardata.height or 0) + (chardata.depth or 0) + local width = chardata.width or 0 + if data then + local tl = data.topleft ; if tl then tl = scale(tl,width,total) end + local tr = data.topright ; if tr then tr = scale(tr,width,total) end + local bl = data.bottomleft ; if bl then bl = scale(bl,width,total) end + local br = data.bottomright ; if br then br = scale(br,width,total) end + chardata.mathkerns = { + topleft = tl, + ropright = tr, + bottomleft = bl, + bottomright = br, + } + else + chardata.mathkerns = nil + end + end + for unicode, data in next, kerns do + kernone(unicode,data) -- withscriptcode(tfmdata,unicode,data,kernone) + -- also smaller + end + end + end + +end + +do + + -- local list = { + -- [0x203E] = { factor = .4 }, -- overbar + -- [0x203E] = { factor = .7 }, -- underbar + -- [0x23DE] = { factor = .4 }, -- overbrace + -- [0x23DF] = { factor = .7 }, -- underbrace + -- [0x23DC] = { factor = .4 }, -- overparent + -- [0x23DD] = { factor = .7 }, -- underparent + -- [0x23B4] = { factor = .4 }, -- overbracket + -- [0x23B5] = { factor = .7 }, -- underbracket + -- } + + -- We can patch the dimensions in-place or we can use additional characters in + -- the private namespace. + + -- local addprivate = fonts.helpers.addprivate + -- local newnextglyph = addprivate(target,formatters["M-N-%H"](nextglyph),newnextdata) + + local 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, -- widebar + [0x0301] = over, -- wideacute + [0x0302] = over, -- widehat + [0x030C] = over, -- widecheck + [0x0306] = over, -- widebreve + [0x0307] = over, -- widedot + [0x030A] = over, -- widering + [0x0303] = over, -- widetilde + [0x20DB] = over, -- widedddot + }, + } + + datasets.accentdimensions = candidates + + local function adapt(c,factor,baseheight,basedepth) +-- if not c.tweaked then + local height = c.height or 0 + local depth = c.depth or 0 + local yoffset = 0 + if factor == "over" then + local h = height - baseheight + yoffset = h - height + height = h + depth = depth - baseheight + elseif factor == "under" then + local d = depth - basedepth + yoffset = depth - d + depth = d + height = height - baseheight + elseif height > 0 then + local h = tonumber(factor) * height + yoffset = h - height + height = h + elseif depth > 0 then + local d = tonumber(factor) * depth + yoffset = depth - d + depth = d + end + c.yoffset = yoffset ~= 0 and yoffset or nil + c.height = height > 0 and height or nil + c.depth = depth > 0 and depth or nil +-- c.tweaked = true +-- end + end + + local function process(target,original,characters,list,baseheight,basedepth) + if list then + for k, v in sortedhash(list) do -- sort for tracing + local c = characters[k] + if c and not c.yoffset then + local factor = v.factor + if factor then + adapt(c,factor,baseheight,basedepth) + local nc = c.next + local nv = 0 + local ns = 0 + while nc do + local c = characters[nc] + if c then + adapt(c,factor,baseheight,basedepth) + nv = nv + 1 + nc = c.next + if not nc then + local hv = c.parts + if hv then + for i=1,#hv do + local c = characters[hv[i].glyph] + if c then + adapt(c,factor,baseheight,basedepth) + ns = ns + 1 + end + end + end + break + end + else + break + end + end + if trace_tweaking then + report_tweak("adapting extensible (%i sizes, %i parts) %U",target,original,k,nv,ns) + end + end + end + end + end + end + + function mathtweaks.accentdimensions(target,original,parameters) + local list = parameters.list or { "over", "under" } + if list then + local characters = target.characters + local baseheight = target.mathparameters.AccentBaseHeight or 0 + local basedepth = target.mathparameters.AccentBaseDepth or 0 + for k, v in sortedhash(list) do -- sort for tracing + local t = type(v) + if t == "string" then + v = candidates[v] + t = type(v) + end + if t == "table" then + process(target,original,characters,v,baseheight,basedepth) + end + end + end + end + +end + +do + + local addprivate = fonts.helpers.addprivate + local privateslot = fonts.helpers.privateslot + + -- function mathtweaks.addrules(target,original,parameters) + -- local characters = target.characters + -- local height = target.mathparameters.OverbarRuleThickness + -- local depth = target.mathparameters.UnderbarRuleThickness + -- local width = target.parameters.emwidth/2 + -- local step = 0.8 * width + -- characters[0x203E] = { -- over + -- width = width, + -- height = height, + -- depth = 0, + -- unicode = 0x203E, + -- commands = { { "rule", height, width } }, + -- parts = { + -- { advance = width, ["end"] = step, glyph = 0x203E, start = 0 }, + -- { advance = width, ["end"] = 0, glyph = 0x203E, start = step, extender = 1 }, + -- } + -- } + -- characters[0x0332] = { -- under + -- width = width, + -- height = 0, + -- depth = depth, + -- yoffset = -depth, + -- unicode = 0x0332, + -- commands = { { "rule", height, width } }, + -- parts = { + -- { advance = width, ["end"] = step, glyph = 0x0332, start = 0 }, + -- { advance = width, ["end"] = 0, glyph = 0x0332, start = step, extender = 1 }, + -- } + -- } + -- end + + function mathtweaks.addrules(target,original,parameters) + local characters = target.characters + local thickness = target.mathparameters.OverbarRuleThickness + local width = target.parameters.emwidth / 2 + local step = 0.8 * width + characters[0x203E] = { -- over + width = width, + height = thickness / 2, + depth = thickness / 2, + yoffset = - thickness / 2, + unicode = 0x203E, + commands = { { "rule", thickness, width } }, + parts = { + { advance = width, ["end"] = step, glyph = 0x203E, start = 0 }, + { advance = width, ["end"] = 0, glyph = 0x203E, start = step, extender = 1 }, + }, + partsorientation = "horizontal", + } + -- + characters[0x0332] = characters[0x203E] + -- + -- lucida lacks them ... + -- + local half = thickness / 2 + local double = thickness * 2 + -- + if not characters[0x23B4] then + local tpiece = addprivate(target,"bracket-piece-top",{ + width = thickness, + height = half, + depth = double, + yoffset = - double, + commands = { { "rule", thickness * 2.5, thickness } }, + }) + characters[0x23B4] = { -- over + width = width, + height = half, + depth = double, + unicode = 0x23B4, + commands = { { "rule", thickness, width } }, + parts = { + { advance = thickness, glyph = tpiece, ["end"] = 0, start = half }, + { advance = width, glyph = 0x203E, ["end"] = step, start = step, extender = 1 }, + { advance = thickness, glyph = tpiece, ["end"] = half, start = 0 }, + }, + partsorientation = "horizontal", + } + end + if not characters[0x23B5] then + local bpiece = addprivate(target,"bracket-piece-bottom",{ + width = thickness, + height = double, + depth = half, + yoffset = - half, + commands = { { "rule", thickness * 2.5, thickness } }, + }) + characters[0x23B5] = { -- over + width = width, + height = double, + depth = half, + unicode = 0x23B5, + commands = { { "rule", thickness, width } }, + parts = { + { advance = thickness, glyph = bpiece, ["end"] = 0, start = half }, + { advance = width, glyph = 0x203E, ["end"] = step, start = step, extender = 1 }, + { advance = thickness, glyph = bpiece, ["end"] = half, start = 0 }, + }, + partsorientation = "horizontal", + } + end + -- + end + + local force = false experiments.register("math.arrows", function(v) force = v end) + + local function tighten(target,unicode,left,right,squeeze,yoffset) + local name = string.formatters["math tightened %U %.3N %.3N %.3N %.3N"](unicode,left,right,squeeze,yoffset) + local slot = privateslot(target,name) + if not slot then + local characters = target.characters + local data = copytable(characters[unicode]) + local width = data.width + data.advance = width + data.width = width * (1-left-right) + data.xoffset = width * -left + if squeeze ~= 1 then + data.effect = { squeeze = squeeze } + end + if yoffset ~= 0 then + data.yoffset = (data.height or 0) * yoffset + end + slot = addprivate(target,name,data) + end + return slot + end + + local function create(target,unicode,list,overloads) + local characters = target.characters + local chardata = characters[unicode] + if chardata then + local endpoint = unicode + while chardata.next do + chardata = characters[chardata.next] + end + if chardata and (force or overloads[unicode] == false or not chardata.parts) then + if not list then + -- chardata.parts = nil -- when we test + 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 + end end - if not mathparameters.FractionDelimiterDisplayStyleSize then - mathparameters.FractionDelimiterDisplayStyleSize = 2.40 * size + end + end + end + + -- Unicode math lacks the arrow snippet while it does have fence snippets. Also, some + -- fonts have a relbar that doesn't match the double arrow. + -- + -- { + -- tweak = "addarrows", + -- list = { [0x3D] = { squeeze = .85, yoffset = .0975 } } + -- }, + -- + -- We have no begin and end snippet, so I played with centering and rules at the edges + -- + -- [0x21A9] = { -- hookleftarrow + -- { glyph = 0x2212, left = slack, extensible = true }, + -- { glyph = 0x21A9, right = slack }, + -- { glyph = 0x2212, right = slack, extensible = true }, + -- } + -- + -- but in the end rejected it. + + local function initialize(left, right, slack) + -- We save some space with locals. When no glyph is given the unicode itself is + -- used which also saves some. + local single = { glyph = 0x2212, left = slack, right = slack, extensible = true } + local double = { glyph = 0x003D, left = slack, right = slack, extensible = true } + local triple = { glyph = 0x2261, left = slack, right = slack, extensible = true } + ----- spacer = { glyph = 0x0020, left = slack, right = slack, extensible = true } + local slackslack = { left = slack, right = slack } + local leftslack = { left = left, right = slack } + local slackright = { left = slack, right = right } + ----- centered = { spacer, { }, spacer } + local centered = false -- the luametatex engine does this + local singleright = { single, slackright } + local leftsingle = { leftslack, single } + return { + -- + [0x002D] = { { left = slack, right = slack, glyph = 0x2212 }, single }, -- rel + -- + [0x2190] = leftsingle, -- leftarrow + [0x219E] = leftsingle, -- twoheadleftarrow + [0x21BC] = leftsingle, -- leftharpoonup + [0x21BD] = leftsingle, -- leftharpoondown + -- + [0x2192] = singleright, -- rightarrow + [0x21A0] = singleright, -- twoheadrightarrow + [0x21C0] = singleright, -- rightharpoonup + [0x21C1] = singleright, -- rightharpoondown + -- + [0x003D] = { slackslack, double }, -- equaltext + [0x2261] = { slackslack, triple }, -- triplerel + [0x27F8] = { leftslack, double }, -- Leftarrow + [0x27F9] = { double, slackright }, -- Rightarrow + -- + [0x21A9] = centered, -- hookleftarrow + [0x21AA] = centered, -- hookrightarrow + [0x21CB] = centered, -- leftrightharpoons + [0x21CC] = centered, -- rightleftharpoons + [0x21C4] = centered, -- rightoverleftarrow + [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 + 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 - elseif properties then - report_math("invalid parameters in font %a",properties.fullname or "?") + 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 + + function mathtweaks.fixradicals(target,original,parameters) + local characters = target.characters + for i=1,#list do + local unicode = list[i] + fix(target,original,characters,unicode) + end + end + + local function fix(target,original,characters,u,l) + local data = characters[u] + if data then + data.innerlocation = l.location == "right" and 2 or 1 + data.innerxoffset = (l.hfactor or 1) * (data.width or 0) + data.inneryoffset = (l.vfactor or 1) * ((data.height or 0) + (data.depth or 0)) + end + end + + function mathtweaks.radicaldegreeanchors(target,original,parameters) + local list = parameters.list + if list then + local characters = target.characters + for unicode, l in sortedhash(list) do -- resolve variants + local u = detail(characters,unicode) or unicode + if type(u) == "table" then + for i=1,#u do + fix(target,original,characters,u[i],l) + end + else + fix(target,original,characters,u,l) + end + end + end + end + +end + +do + + local done = nil + + local function fix(target,original,characters,unicode,axis) + if done[unicode] then + return + end + done[unicode] = true + local data = characters[unicode] + if data then + local height = data.height or 0 + local depth = data.depth or 0 + if trace_tweaking then + report_tweak("swapping height and depth of %U",target,original,unicode) + end + local half = (height + depth)/2 + if data.rorrim then + -- the original does the magic else - report_math("invalid parameters in font") + 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 - elseif properties then - report_math("no parameters in font %a",properties.fullname or "?") else - report_math("no parameters and properties in font") + report_tweak("no need to fix the integral extensible",target,original) end end + end -function mathematics.overloadparameters(target,original) - local mathparameters = target.mathparameters - if mathparameters and next(mathparameters) then - local goodies = target.goodies - if goodies then - for i=1,#goodies do - local goodie = goodies[i] - local mathematics = goodie.mathematics - local parameters = mathematics and mathematics.parameters - if parameters then - if trace_defining then - report_math("overloading math parameters in %a @ %p",target.properties.fullname,target.parameters.size) - end - for name, value in next, parameters do - local tvalue = type(value) - if tvalue == "string" then - report_math("comment for math parameter %a: %s",name,value) +do + + local list = { 0x2061, 0x2062, 0x2063, 0x2064 } + + datasets.wipecues = list + + function mathtweaks.wipecues(target,original,parameters) + local characters = target.characters + local tobewiped = parameters.list or list + local done = false + for i=1,#tobewiped do + local unicode = tobewiped[i] + characters[unicode] = { + width = 0, + height = 0, + depth = 0, + unicode = unicode, + } + if not trace_tweaking then + done = true + elseif done then + done[unicode] = true + else + done = { [unicode] = true } + end + end + feedback_tweak("wipecues",target,original,done) + end + +end + +do + + local mapping = { + [0x002F] = 0x2044, + } + + datasets.fixslashes = mapping + + function mathtweaks.fixslashes(target,original,parameters) + local characters = target.characters + -- local done = false + for normal, weird in sortedhash(mapping) do + local normalone = characters[normal] + local weirdone = characters[weird] + if normalone and weirdone and not normalone.next then + normalone.next = weirdone.next + -- if not trace_tweaking then + -- done = true + -- elseif done then + -- done[normal] = true + -- else + -- done = { [normal] = true } + -- end + end + weirdone = copytable(normalone) + characters[weird] = weirdone + weirdone.unicode = weird + end + -- feedback_tweak("fixslashes",target,original,done) + if trace_tweaking then + report_tweak("slashes fixed",target,original) + end + end + +end + +do -- see pagella for an extensive example + + local nps = fonts.helpers.newprivateslot + + local mapping = { + [0x0300] = { 0x0060, false, nps("flat 0x0060 1") }, + [0x0308] = { 0x00A8, false, nps("flat 0x00A8 1") }, + [0x0304] = { 0x00AF, false, nps("flat 0x00AF 1") }, + [0x0301] = { 0x00B4, false, nps("flat 0x00B4 1") }, + [0x0302] = { 0x02C6, true, nps("flat 0x02C6 1") }, + [0x030C] = { 0x02C7, true, nps("flat 0x02C7 1") }, + [0x0306] = { 0x02D8, false, nps("flat 0x02D8 1") }, + [0x0307] = { 0x02D9, false, nps("flat 0x02D9 1") }, + [0x030A] = { 0x02DA, false, nps("flat 0x02DA 1") }, + [0x0303] = { 0x02DC, true, nps("flat 0x02DC 1") }, + [0x20DB] = { 0x20DB, false, nps("flat 0x20DB 1") }, + } + + datasets.fixaccents = mapping + datasets.extendaccents = mapping + datasets.flattenaccents = mapping + datasets.copyaccents = mapping + + function mathtweaks.fixaccents(target,original,parameters) + local characters = target.characters + local done = false + for stretching, entry in sortedhash(mapping) do + local alias = entry[1] + local stretchingdata = characters[stretching] + if stretchingdata and stretchingdata.width == 0 then + local topanchor = stretchingdata.topanchor or 0 + local width = -topanchor + topanchor = width/2 + stretchingdata.width = width + stretchingdata.advance = 0 + stretchingdata.topanchor = topanchor + stretchingdata.commands = { rightcommand[width + topanchor], charcommand[stretching] } + if not trace_tweaking then + done = true + elseif done then + done[stretching] = true + else + done = { [stretching] = true } + end + end + end + feedback_tweak("fixaccents",target,original,done) + end + + -- all true|number false + + function mathtweaks.extendaccents(target,original,parameters) + local characters = target.characters + local all = parameters.all + local count = tonumber(all) + local done = false + for stretching, entry in sortedhash(mapping) do + local extend = entry[2] + if extend then + local last = characters[stretching] + local cnt = 1 + local okay = false + while last do + if all or (count and cnt > count) then + last.extensible = true + local flataccent = last.flataccent + if flataccent then + characters[flataccent].extensible = true + okay = true + end + end + local n = last.next + if n then + last = characters[n] + else + last.extensible = true + local flataccent = last.flataccent + if flataccent then + characters[flataccent].extensible = true + okay = true + end + break + end + cnt = cnt + 1 + end + if okay then + if not trace_tweaking then + done = true + elseif done then + done[stretching] = true + else + done = { [stretching] = true } + end + end + end + end + feedback_tweak("extendaccents",target,original,done) + end + + -- force true false + -- height factor 0.8 + -- offset factor 0.9|calculated + -- squeeze factor 0.1|calculated + + function mathtweaks.flattenaccents(target,original,parameters) + local characters = target.characters + local force = parameters.force + local squeeze = parameters.squeeze or 0.8 + local ofactor = parameters.offset or (squeeze/2) + local hfactor = parameters.height or (1 - ofactor) + local done = false + for stretching, entry in sortedhash(mapping) do + local last = characters[stretching] + while last do + if force or not last.flataccent then + local slot = entry[3] + local data = copytable(last) + local height = data.height or 0 + data.effect = { squeeze = squeeze } + data.height = hfactor * height + data.yoffset = ofactor * height + characters[slot] = data + last.flataccent = slot + if not trace_tweaking then + done = true + elseif done then + done[stretching] = true + else + done = { [stretching] = true } + end + end + local n = last.next + if n then + last = characters[n] + else + break + end + end + end + feedback_tweak("flattenaccents",target,original,done) + end + + function mathtweaks.copyaccents(target,original,parameters) + local characters = target.characters + local done = false + for stretching, entry in sortedhash(mapping) do + local alias = entry[1] + if alias ~= stretching then + local stretchingdata = characters[stretching] + if stretchingdata then + -- we need to nil [x|y]offsets + characters[alias] = { + width = stretchingdata.width, + height = stretchingdata.height, + depth = stretchingdata.depth, + next = stretchingdata.next, + commands = { charcommand[stretching] }, + topanchor = stretchingdata.topanchor, + -- unicode = stretching, -- when we alias to combiners + unicode = alias, -- when we keep the original + } + if not trace_tweaking then + done = true + elseif done then + done[stretching] = true + else + done = { [stretching] = true } + end + end + end + end + feedback_tweak("copyaccents",target,original,done) + end + +end + +do + + local single <const> = 0x003D + local double <const> = 0x2A75 + local triple <const> = 0x2A76 + + function mathtweaks.addequals(target,original,parameters) + local characters = target.characters + local basechar = characters[single] + local width = basechar.width + local height = basechar.height + local depth = basechar.depth + local advance = (parameters.advance or 1/20) * width + local char = charcommand[single] + local left = leftcommand[advance] + characters[double] = { + unicode = double, + width = 2*width - 1*advance, + height = height, + depth = depth, +-- callback = "devirtualize", + commands = { char, left, char }, + } + characters[triple] = { + unicode = triple, + width = 3*width - 2*advance, + height = height, + depth = depth, +-- callback = "devirtualize", + commands = { char, left, char, left, char }, + } + if trace_tweaking then + report_tweak("double %U and triple %U equals added",target,original,double,triple) + end + end + +end + +do + + -- If we really want, we can have variants that also match radicals but in practice + -- radicals and actuarians are never seen together. We could also have a smaller + -- extender. + + local nps = fonts.helpers.newprivateslot + + local radical <const> = 0x0221A + local actuarian <const> = nps("delimited right annuity") -- 0x020E7 + local nairautca <const> = nps("delimited left annuity" ) + local placehold <const> = nps("delimited ghost annuity") + + function mathtweaks.addactuarian(target,original,parameters) + local characters = target.characters + local parameters = target.parameters + local linewidth = target.MathConstants.RadicalRuleThickness -- make option + local basechar = characters[radical] + local baseheight = (basechar.height or 0)/2 + local basedepth = (basechar.depth or 0)/2 + local basetotal = baseheight + basedepth + local used = baseheight + -- + characters[0x020E7] = { + width = 6*linewidth, + height = baseheight, + depth = basedepth, + unicode = actuarian, + callback = "devirtualize", + commands = { + upcommand[baseheight-4*linewidth], + { "rule", linewidth, 4*linewidth }, + downcommand[basetotal/2-linewidth], + { "rule", basetotal/2, linewidth }, + }, + } + -- + characters[actuarian] = { + width = 2*linewidth, + height = baseheight, + depth = basedepth, + unicode = actuarian, + callback = "devirtualize", + commands = { + downcommand[basedepth], + { "rule", basetotal, linewidth }, + }, + parts = { + { + advance = basetotal, + ["end"] = used, + glyph = actuarian, + start = 0, + }, + { + advance = basetotal, + ["end"] = 0, + extender = 1, + glyph = actuarian, + start = used, + }, + } + } + characters[placehold] = { + width = 2*linewidth, + height = baseheight, + depth = basedepth, + unicode = actuarian, -- whatever + callback = "devirtualize", + commands = { + rightcommand[linewidth], + downcommand[basedepth], + { "rule", basetotal, 0 }, + }, + } + characters[nairautca] = { + width = 2*linewidth, + height = baseheight, + depth = basedepth, + unicode = actuarian, -- whatever + callback = "devirtualize", + -- commands = { + -- rightcommand[linewidth], + -- downcommand[basedepth], + -- { "rule", basetotal, linewidth }, + -- }, + commands = { + rightcommand[linewidth], + upcommand[baseheight-4*linewidth], + { "rule", 4*linewidth, linewidth }, + }, + parts = { + { + advance = basetotal, + ["end"] = used, + extender = 1, + glyph = placehold, + start = 0, + }, + { + advance = basetotal, + ["end"] = 0, + glyph = nairautca, + start = used, + }, + } + } + if trace_tweaking then + report_tweak("actuarian %U added",target,original,actuarian) + end + end + +end + +do + + -- todo: make callback because we can delay it but then we need to stack + -- callbacks + + local nps = fonts.helpers.newprivateslot + + local list = { + -- { 0x0300, nps("delimited right grave"), nps("delimited ghost grave") }, + { 0x0308, nps("delimited right ddot"), nps("delimited ghost ddot") }, + { 0x0304, nps("delimited right bar"), nps("delimited ghost bar") }, + -- { 0x0301, nps("delimited right acute"), nps("delimited ghost acute") }, + { 0x0302, nps("delimited right hat"), nps("delimited ghost hat") }, + { 0x030C, nps("delimited right check"), nps("delimited ghost check") }, + { 0x0306, nps("delimited right breve"), nps("delimited ghost breve") }, + { 0x0307, nps("delimited right dot"), nps("delimited ghost dot") }, + { 0x030A, nps("delimited right ring"), nps("delimited ghost ring") }, + { 0x0303, nps("delimited right tilde"), nps("delimited ghost tilde") }, + { 0x20DB, nps("delimited right dddot"), nps("delimited ghost dddot") }, + { 0x2020, nps("delimited right dagger"), nps("delimited ghost dagger") }, + { 0x2021, nps("delimited right ddagger"), nps("delimited ghost ddagger") }, + { 0x2217, nps("delimited right ast"), nps("delimited ghost ast") }, + { 0x22C6, nps("delimited right star"), nps("delimited ghost star") }, + + { 0x231C, nps("delimited left upper corner"), nps("delimited ghost upper corner") }, + { 0x231D, nps("delimited right upper corner"), nps("delimited ghost upper corner") }, + { 0x231E, nps("delimited left lower corner"), nps("delimited ghost lower corner"), true }, + { 0x231F, nps("delimited right lower corner"), nps("delimited ghost lower corner"), true }, + } + + function mathtweaks.addfourier(target,original,parameters) + local characters = target.characters + for i=1,#list do + local entry = list[i] + local basecode = entry[1] + local fouriercode = entry[2] + local movecode = entry[3] + local reverse = entry[4] + local basechar = characters[basecode] + if basechar then + local scale = parameters.scale or 1 + local variant = parameters.variant + if variant then + for i=1,variant do + local okay = basechar.next + if okay then + basecode = okay + basechar = characters[basecode] else - local oldvalue = mathparameters[name] - local newvalue = oldvalue - if oldvalue then - if tvalue == "number" then - newvalue = value - elseif tvalue == "function" then - newvalue = value(oldvalue,target,original) - elseif not tvalue then - newvalue = nil + break + end + end + end + local baseheight = scale * (basechar.height or 0) + local basedepth = scale * (basechar.depth or 0) + local basewidth = scale * (basechar.width or 0) + local used = baseheight/2 + local total = baseheight + basedepth +if reverse then + used = total / 2 -- basedepth / 2 +end + characters[movecode] = { + width = basewidth, + height = used, + unicode = basecode, + -- callback = "devirtualize", + commands = { + downcommand[used], + { "rule", used, 0 }, + }, + } + local parts = { + { + advance = used, + ["end"] = used, + extender = 1, + glyph = movecode, + start = used, + }, + { + advance = total, + ["end"] = 0, + glyph = fouriercode, + start = total, + }, + } + if reverse then + parts[1], parts[2] = parts[2], parts[1] + end + characters[fouriercode] = { + width = basewidth, + height = baseheight, + depth = basedepth, + unicode = basecode, + -- callback = "devirtualize", + commands = { + scale == 1 and charcommand[basecode] or { "slot", 0, basecode, scale, scale }, + }, + partsorientation = "vertical", + parts = parts, + } + if trace_tweaking then + report_tweak("fourier %U added using %U",target,original,basecode,fouriercode) + end + end + end + end + +end + +do + + -- \im{\left\Uchar"007C \frac{1}{2} \right\Uchar"007C} + -- \im{\left\Uchar"2016 \frac{1}{2} \right\Uchar"2016} + -- \im{\left\Uchar"2980 \frac{1}{2} \right\Uchar"2980} + + local single <const> = 0x007C + local double <const> = 0x2016 + local triple <const> = 0x2980 + + -- local nps = fonts.helpers.newprivateslot + -- + -- local function variantlist(characters,unicode,chardata,what,total,used) + -- local parenthesis = characters[0x28].next + -- local width = chardata.width + -- local height = chardata.height + -- local depth = chardata.depth + -- local total = height + depth + -- local count = 1 + -- while parenthesis do + -- local private = nps(what .. " size " .. count) + -- local pardata = characters[parenthesis] + -- local parheight = pardata.height + -- local pardepth = pardata.depth + -- local scale = (parheight+pardepth)/total + -- local offset = - pardepth + scale * depth + -- chardata.next = private + -- chardata = { + -- unicode = unicode, + -- width = width, + -- height = parheight, + -- depth = pardepth, + -- commands = { + -- { "offset", 0, offset, unicode, 1, scale } + -- }, + -- } + -- characters[private] = chardata + -- parenthesis = pardata.next + -- if paranthesis then + -- pardata = characters[parenthesis] + -- end + -- count = count + 1 + -- end + -- chardata.parts = { + -- { + -- advance = total, + -- ["end"] = used, + -- glyph = unicode, + -- start = 0, + -- -- start = used/5, + -- }, + -- { + -- advance = total, + -- -- ["end"] = 0, + -- ["end"] = used/5, -- prevents small gap with inward curved endpoints + -- extender = 1, + -- glyph = unicode, + -- start = used, + -- }, + -- } + -- chardata.partsorientation = "vertical" + -- end + + local function variantlist(unicode,chardata,total,used) + chardata.varianttemplate = 0x0028 + chardata.parts = { + { + advance = total, + ["end"] = used, + glyph = unicode, + start = 0, + -- start = used/5, + }, + { + advance = total, + -- ["end"] = 0, + ["end"] = used/5, -- prevents small gap with inward curved endpoints + extender = 1, + glyph = unicode, + start = used, + }, + } + chardata.partsorientation = "vertical" + end + + function mathtweaks.addbars(target,original,parameters) + local characters = target.characters + local basechar = characters[single] + local width = basechar.width + local height = basechar.height + local depth = basechar.depth + local advance = (parameters.advance or 1/10) * width + local used = 1.2*height -- large overlap because no smaller pieces + local total = height + depth + -- + -- variantlist(characters,single,basechar,"single bar",total,used) + variantlist(single,basechar,total,used) + -- + basechar = { + unicode = double, + width = 2*width - 1*advance, + height = height, + depth = depth, +-- callback = "devirtualize", + commands = { + charcommand[single], + leftcommand[advance], + charcommand[single], + }, + } + characters[double] = basechar + -- variantlist(characters,double,basechar,"double bar",total,used) + variantlist(double,basechar,total,used) + -- + basechar = { + unicode = triple, + width = 3*width - 2*advance, + height = height, + depth = depth, +-- callback = "devirtualize", + commands = { + charcommand[single], + leftcommand[advance], + charcommand[single], + leftcommand[advance], + charcommand[single], + }, + } + characters[triple] = basechar + -- variantlist(characters,triple,basechar,"triple bar",total,used) + variantlist(triple,basechar,total,used) + -- + 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 <const> = 0x002E + local sraised <const> = 0x22C5 + + local tnormal <const> = 0x2026 + local traised <const> = 0x22EF + + function mathtweaks.fixellipses(target,original,parameters) + local characters = target.characters + local function fix(normal,raised) + local normaldata = characters[normal] + if normaldata then + local raiseddata = copytable(normaldata) + characters[raised] = raiseddata + raiseddata.unicode = raised + local height = raiseddata.height + local yoffset = (parameters.yoffset or 2) * height + raiseddata.yoffset = yoffset + raiseddata.height = height + yoffset + if trace_tweaking then + report_tweak("taking %U from %U",target,original,raised,normal) + end + end + end + fix(snormal,sraised) + fix(tnormal,traised) + end + +end + +do + + -- For Ton, who needs the high minus and plus for calculator signs in Dutch + -- school math books. + + local list = { + { 0x207A, 0x002B, true }, + { 0x207B, 0x2212, true }, + { 0x208A, 0x002B, false }, + { 0x208B, 0x2212, false }, + } + + datasets.addscripts = list + + local function add(target,original,characters,unicode,template,super,baseheight,scale) + if not characters[unicode] then + local origdata = characters[template] + if origdata then + local width = scale * (origdata.width or 0) + local height = scale * (origdata.height or 0) + local depth = scale * (origdata.depth or 0) + local half = - (height + depth) / 2 + local offset = super and baseheight/2 or -baseheight/4 + characters[unicode] = { + width = width, + height = height + offset, + depth = depth - offset, + unicode = unicode, + commands = { + { "offset", 0, offset, template, scale, scale } + }, + } + if trace_tweaking then + report_tweak("adding script %U scaled %0.3f",target,original,unicode,scale) + end + -- no need for smaller + end + end + end + + function mathtweaks.addscripts(target,original,parameters) + local characters = target.characters + local baseheight = target.mathparameters.AccentBaseHeight + local scaledown = parameters.scale or target.mathparameters.ScriptScriptPercentScaleDown / 100 + for i=1,#list do + local entry = list[i] + if entry then + add(target,original,characters,entry[1],entry[2],entry[3],baseheight,scaledown) + end + end + end + +end + +do + + -- We started with the list that xits has in rtlm but most of them can be derived from + -- the database, and others need to be added. + + -- Checked while watching/listening to Dave Matthews Band: The Central Park Concert + -- (with superb solos by Warren Haynes), a DVD I bought around when we started with the + -- LUATEX advanture. + + local mirrors = { + + [0x0002F] = true, -- slashes + [0x0005C] = true, + [0x000F7] = true, + [0x02044] = true, + [0x02215] = true, + + [0x02032] = true, -- primes + [0x02033] = true, + [0x02034] = true, + [0x02057] = true, + [0x02035] = true, + [0x02036] = true, + [0x02037] = true, + + [0x0221A] = true, -- radicals + [0x0221B] = true, + [0x0221C] = true, + [0x0221D] = true, + + [0x0222B] = true, -- integrals + [0x0222C] = true, + [0x0222D] = true, + [0x0222E] = true, + [0x0222F] = true, + [0x02230] = true, + [0x02231] = true, + [0x02232] = true, + [0x02233] = true, + + [0x02A0A] = true, -- seen in xits (to be checked) + [0x02A0B] = true, + [0x02A0C] = true, + [0x02A0D] = true, + [0x02A0E] = true, + + [0x02140] = true, + [0x02201] = true, + [0x02202] = true, + [0x02203] = true, + [0x02204] = true, + [0x02211] = true, + + [0x02239] = true, + [0x0225F] = true, + [0x0228C] = true, + [0x022A7] = true, + [0x022AA] = true, + [0x022AC] = true, + [0x022AD] = true, + [0x022AE] = true, + [0x022AF] = true, + [0x022F5] = true, + [0x022F8] = true, + [0x022F9] = true, + [0x022FF] = true, + [0x02320] = true, + [0x02321] = true, + [0x027C0] = true, + [0x029DC] = true, + [0x029F4] = true, + + [0x02A0F] = true, + [0x02A10] = true, + [0x02A11] = true, + [0x02A12] = true, + [0x02A13] = true, + [0x02A14] = true, + [0x02A15] = true, + [0x02A16] = true, + [0x02A17] = true, + [0x02A18] = true, + [0x02A19] = true, + [0x02A1A] = true, + [0x02A1B] = true, + [0x02A1C] = true, + [0x02A20] = true, + + [0x02A74] = true, + [0x02AA3] = true, + [0x02AE2] = true, + [0x02AE6] = true, + [0x1D715] = true, + } + + local new = fonts.helpers.newprivateslot + + local function add(target,original,characters,unicode,what) + local data = characters[unicode] + if data then + if not data.mirror then + local slot = new("mirror."..unicode) + local mirror = copytable(data) + data.mirror = slot + mirror.rorrim = unicode -- so we can check later + mirror.commands = { + { "offset", data.width, 0, unicode, -1, 1 } + } + if trace_tweaking then + report_tweak("adding mirror %U (%s)",target,original,unicode,what) + end + characters[slot] = mirror + elseif trace_tweaking then + report_tweak("skipping mirror %U (%s)",target,original,unicode,what) + end + local parts = data.parts + if parts then + for i=1,#parts do + add(target,original,characters,parts[i],"hpart") + end + end + local smaller = data.smaller + if smaller then + add(target,original,characters,"smaller") + end + local next = data.next + if next then + add(target,original,characters,next,"next") + end + end + end + + -- todo: also check the rtlm table if present + + function mathtweaks.addmirrors(target,original,parameters) + local characters = target.characters + -- for unicode, detail in sortedhash(characters) do + for unicode, detail in next, characters do + local data = chardata[unicode] + if data and data.mirror then + add(target,original,characters,unicode,"mirror") + end + end + for unicode, detail in sortedhash(mirrors) do + if characters[unicode] then + add(target,original,characters,unicode,"character") + elseif trace_tweaking then + report_tweak("ignoring mirror %U (%s)",target,original,unicode,what) + end + end + end + +end + +do + + local reported = { } + + function mathtweaks.version(target,original,parameters) + local metadata = original.shared.rawdata.metadata + if metadata then + local version = string.strip(metadata.version or "") -- some have trailing spaces + if version then + local expected = parameters.expected + local fontname = metadata.fontname or false + local message = parameters.message + -- version = tonumber(string.match(version,"%d+.%d+")) + if version ~= expected and not reported[fontname] then + report_tweak("version %a found, version %a expected",target,original,version,expected) + elseif trace_tweaking then + report_tweak("version %a found",target,original,version) + end + if message and message ~= "" and not reported[fontname] then + report_tweak() + report_tweak("%s",target,original,message) + report_tweak() + end + reported[fontname] = true + target.tweakversion = version + end + end + end + +end + +do + + function mathtweaks.parameters(target,original,parameters) + local newparameters = parameters.list + local oldparameters = target.mathparameters + if newparameters and oldparameters then + newparameters = copytable(newparameters) + scaleparameters(newparameters,target.parameters) + for name, newvalue in next, newparameters do + oldparameters[name] = newvalue + end + end + end + + function mathtweaks.bigslots(target,original,parameters) + local list = parameters.list + if list then + target.bigslots = list + end + end + +end + +-- do +-- +-- function mathtweaks.diagnose(target,original,parameters) +-- local characters = target.characters +-- for k, v in sortedhash(characters) do +-- local italic = v.italic +-- if italic then +-- report_tweak("italics: %C %p",target,original,k,italic) +-- end +-- end +-- end +-- +-- end + +do + + function mathtweaks.setoptions(target,original,parameters) + local setlist = parameters.set or parameters.list + local resetlist = parameters.reset + if setlist or resetlist then + local properties = target.properties + local codes = tex.mathcontrolcodes + local oldcontrol = texget("mathfontcontrol") + local newcontrol = oldcontrol + -- todo: reset + if resetlist then + for i=1,#resetlist do + local v = tonumber(codes[resetlist[i]]) + if v then + newcontrol = newcontrol & (not v) + end + end + end + if setlist then + for i=1,#setlist do + local v = tonumber(codes[setlist[i]]) + if v then + newcontrol = newcontrol | v + end + end + end + newcontrol = newcontrol | codes.usefontcontrol + properties.mathcontrol = newcontrol + target.mathcontrol = newcontrol + if trace_tweaking then + report_tweak("forcing math font options 0x%08X instead of 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) + if not trace_tweaking then + done = true + elseif done then + done[r] = true + else + done = { [r] = true } end - if trace_defining and oldvalue ~= newvalue then - report_math("overloading math parameter %a: %S => %S",name,oldvalue,newvalue) + end + end + end + end + end + feedback_tweak("setovershoots",target,original,done) + end + end + + -- there is no real need for thios but let's play nice with memory anyway + + local efindex = 0 + local effects = setmetatableindex (function (t,k) + efindex = efindex + 1 + local v = "tweakreplacealphabets" .. efindex + local e = fonts.specifiers.presetcontext(v,"",k) + -- print(k,v,e) + t[k] = v + return v + end) + + function mathtweaks.replacealphabets(target,original,parameters) + local list = parameters.list + if list then + local features = target.specification.features.normal + local definedfont = fonts.definers.internal + local copiedglyph = fonts.handlers.vf.math.copy_glyph + -- does a deep copy, including parts and so + local getsubstitution = fonts.handlers.otf.getsubstitution + local fontdata = fonts.hashes.identifiers + -- + local fonts = target.fonts + local size = target.size + local characters = target.characters + if not fonts then + fonts = { } + target.fonts = fonts + end + if #fonts == 0 then + fonts[1] = { id = 0, size = size } -- self, will be resolved later + end + for i=1,#list do + local entry = list[i] + local filename = entry.filename or parameters.filename + local feature = entry.feature + local thesource = entry.source + local thetarget = entry.target or thesource + if thesource and thetarget then + local sourcerange = type(thesource) == "table" and thesource or blocks[thesource] -- .gaps + local targetrange = type(thetarget) == "table" and thetarget or blocks[thetarget] -- .gaps + if sourcerange and targetrange then + local firsttarget = targetrange.first + local firstsource = sourcerange.first + local lastsource = sourcerange.last or firstsource + if firstsource and firsttarget then + local offset = firsttarget - firstsource + if filename then + local rscale = entry.rscale or 1 -- todo + size = size * rscale -- maybe use scale in vf command + -- load font, todo: set language and script, the effect hack is ugly + local fullname = filename + local effect = features.effect + if effect then + fullname = fullname .. "*" .. effects["effect={"..effect.."}"] + end + local id = definedfont { + name = fullname, + size = size, + } + local chars = fontchars[id] + local dropin = fontdata[id] + local index = 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 + for s=firstsource,lastsource do + local t = s + offset + local sourceunicode = mathgaps[s] or s + if chars[sourceunicode] then + local targetunicode = mathgaps[t] or t + if feature then + sourceunicode = getsubstitution(dropin,sourceunicode,feature,true,"math","dflt") or sourceunicode + end +-- if trace_tweaking then +-- report_tweak("copying %s %U from file %a to %s %U",target,original,thesource,sourceunicode,filename,thetarget,targetunicode) +-- end + characters[targetunicode] = copiedglyph(target,characters,chars,sourceunicode,index) + end + end + -- + 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 + -- + elseif feature then + for s=firstsource,lastsource do + local t = s + offset + local sourceunicode = mathgaps[s] or s + local targetunicode = mathgaps[t] or t + local variant = getsubstitution(original,sourceunicode,feature,true,"math","dflt") + local data = characters[variant] + if data then +-- if trace_tweaking then +-- report_tweak("copying %s %U from feature %a to %s %U",target,original,thesource,sourceunicode,feature,thetarget,targetunicode) +-- end + characters[targetunicode] = copytable(data) + end end else - report_math("invalid math parameter %a",name) + for s=firstsource,lastsource do + local t = s + offset + local sourceunicode = mathgaps[s] or s + local targetunicode = mathgaps[t] or t + if sourceunicode ~= targetunicode then + local data = characters[sourceunicode] + if data then +-- if trace_tweaking then +-- report_tweak("copying %s %U to %s %U",target,original,thesource,sourceunicode,thetarget,targetunicode) +-- end + characters[targetunicode] = copytable(data) + end + end + end end - mathparameters[name] = newvalue + else + -- error end end end end end end + + function mathtweaks.fallbacks(target,original,parameters) + local fallbacks = target.specification.fallbacks + if fallbacks then + local definitions = fonts.collections.definitions[fallbacks] + if definitions then + local list = { } + for i=1,#definitions do + local definition = definitions[i] + -- local check = definition.check + -- local force = definition.force + local first = definition.start + local last = definition.stop + local offset = definition.offset or first + list[#list+1] = { + filename = definition.font, + rscale = definition.rscale or 1, + source = { first = first, last = last }, + target = { first = offset, last = offset + (last - first) }, + } + end + mathtweaks.replacealphabets(target,original,{ + tweak = "replacealphabets", + list = list, + } ) + end + end + end + end -local function applytweaks(when,target,original) - local goodies = original.goodies - if goodies then - for i=1,#goodies do - local goodie = goodies[i] - local mathematics = goodie.mathematics - local tweaks = mathematics and mathematics.tweaks - if type(tweaks) == "table" then - tweaks = tweaks[when] - if type(tweaks) == "table" then - if trace_defining then - report_math("tweaking math of %a @ %p (%s)",target.properties.fullname,target.parameters.size,when) +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 - for i=1,#tweaks do - local tweak= tweaks[i] - local tvalue = type(tweak) - if tvalue == "function" then - tweak(target,original) + 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) - local mathparameters = target.mathparameters -- why not hasmath - if mathparameters then + if use_math_goodies and tweakable(target) then applytweaks("beforecopying",target,original) end end function mathematics.tweakaftercopyingfont(target,original) - local mathparameters = target.mathparameters -- why not hasmath - if mathparameters then + if use_math_goodies and tweakable(target) then applytweaks("aftercopying",target,original) end end -sequencers.appendaction("mathparameters","system","mathematics.scaleparameters") -sequencers.appendaction("mathparameters","system","mathematics.checkaccentbaseheight") -- should go in lfg instead -sequencers.appendaction("mathparameters","system","mathematics.checkprivateparameters") -- after scaling ! -sequencers.appendaction("mathparameters","system","mathematics.overloadparameters") +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) -sequencers.appendaction("beforecopyingcharacters","system","mathematics.tweakbeforecopyingfont") -sequencers.appendaction("aftercopyingcharacters", "system","mathematics.tweakaftercopyingfont") +do --- no, it's a feature now (see good-mth): --- --- sequencers.appendaction("aftercopyingcharacters", "system","mathematics.overloaddimensions") + local defaults = { + { + source = "uppercasescript", + target = "uppercasecalligraphic", + }, + { + source = "lowercasescript", + target = "lowercasecalligraphic", + }, + { + source = "uppercaseboldscript", + target = "uppercaseboldcalligraphic", + }, + { + source = "lowercaseboldscript", + target = "lowercaseboldcalligraphic", + }, + } --- a couple of predefined tweaks: + 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 -local tweaks = { } -mathematics.tweaks = tweaks + target.properties.mathitalics = false + target.properties.textitalics = false + ::NEXTSTEP:: + -- more to come + end + end --- helpers +end -local setmetatableindex = table.setmetatableindex +function mathematics.beforepassingfonttotex(target) + if tweakable(target) then + applytweaks("beforepassing",target,target) + end +end -local getfontoffamily = tex.getfontoffamily +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 ! -local fontcharacters = fonts.hashes.characters -local extensibles = utilities.storage.allocate() -fonts.hashes.extensibles = extensibles +sequencers.appendaction("beforecopyingcharacters","system","mathematics.tweakbeforecopyingfont") +sequencers.appendaction("aftercopyingcharacters", "system","mathematics.tweakaftercopyingfont") +sequencers.appendaction("aftercopyingcharacters", "system","mathematics.checkaftercopyingfont") +sequencers.appendaction("beforepassingfonttotex", "system","mathematics.beforepassingfonttotex") -local chardata = characters.data -local extensibles = mathematics.extensibles +-- 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) @@ -261,6 +3796,8 @@ 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] @@ -279,15 +3816,7 @@ local function extensiblecode(font,unicode) if not char then return unknown end - if character.horiz_variants then - if character.vert_variants then - return { e_mixed, code, character } - else - local m = char.mathextensible - local e = m and extensibles[m] - return e and { e, code, character } or unknown - end - elseif character.vert_variants then + if character.parts then local m = char.mathextensible local e = m and extensibles[m] return e and { e, code, character } or unknown @@ -329,19 +3858,19 @@ local function horizontalcode(family,unicode) local loffset = 0 local roffset = 0 if kind == e_left then - local charlist = data[3].horiz_variants + 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].horiz_variants + 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].horiz_variants + local charlist = data[3].parts if charlist then local left = charlist[1] local right = charlist[#charlist] @@ -355,124 +3884,68 @@ end mathematics.extensiblecode = extensiblecode mathematics.horizontalcode = horizontalcode -interfaces.implement { +interfaces.implement { -- can be public with two times "integerargument" name = "extensiblecode", arguments = { "integer", "integer" }, actions = { extensiblecode, context } } -interfaces.implement { +interfaces.implement { -- can be public with two times "integerargument" name = "horizontalcode", arguments = { "integer", "integer" }, actions = function(family,unicode) local kind, loffset, roffset = horizontalcode(family,unicode) - texsetdimen("scratchleftoffset", loffset) - texsetdimen("scratchrightoffset",roffset) + texsetdimen(d_scratchleftoffset, loffset) + texsetdimen(d_scratchrightoffset,roffset) context(kind) end } - -local stack = { } - -function mathematics.registerfallbackid(n,id,name) - if trace_collecting then - report_math("resolved fallback font %i, name %a, id %a, used %a", - n,name,id,fontproperties[id].fontname) +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 - stack[#stack][n] = id + return unicode end -interfaces.implement { -- will be shared with text - name = "registerfontfallbackid", - arguments = { "integer", "integer", "string" }, - actions = mathematics.registerfallbackid, -} - -function mathematics.resolvefallbacks(target,specification,fallbacks) - local definitions = fonts.collections.definitions[fallbacks] - if definitions then - local size = specification.size -- target.size - local list = { } - insert(stack,list) - context.pushcatcodes("prt") -- context.unprotect() - for i=1,#definitions do - local definition = definitions[i] - local name = definition.font - local features = definition.features or "" - local size = size * (definition.rscale or 1) - context.font_fallbacks_register_math(i,name,features,size) - if trace_collecting then - report_math("registering fallback font %i, name %a, size %a, features %a",i,name,size,features) - end - end - context.popcatcodes() - end -end - -function mathematics.finishfallbacks(target,specification,fallbacks) - local list = remove(stack) - if list and #list > 0 then - local definitions = fonts.collections.definitions[fallbacks] - if definitions and #definitions > 0 then - if trace_collecting then - report_math("adding fallback characters to font %a",specification.hash) - end - local definedfont = fonts.definers.internal - local copiedglyph = fonts.handlers.vf.math.copy_glyph - local fonts = target.fonts - local size = specification.size -- target.size - local characters = target.characters - if not fonts then - fonts = { } - target.fonts = fonts - end - if #fonts == 0 then - fonts[1] = { id = 0, size = size } -- self, will be resolved later - end - local done = { } - for i=1,#definitions do - local definition = definitions[i] - local name = definition.font - local start = definition.start - local stop = definition.stop - local gaps = definition.gaps - local check = definition.check - local force = definition.force - local rscale = definition.rscale or 1 - local offset = definition.offset or start - local id = list[i] - if id then - local index = #fonts + 1 - fonts[index] = { id = id, size = size } - local chars = fontchars[id] - local function remap(unic,unicode,gap) - if check and not chars[unicode] then - return - end - if force or (not done[unic] and not characters[unic]) then - if trace_collecting then - report_math("replacing math character %C by %C using vector %a and font id %a for %a%s%s", - unic,unicode,fallbacks,id,fontproperties[id].fontname,check and ", checked",gap and ", gap plugged") - end - characters[unic] = copiedglyph(target,characters,chars,unicode,index) - done[unic] = true - end - end - local step = offset - start - for unicode = start, stop do - remap(unicode + step,unicode,false) - end - if gaps then - for unic, unicode in next, gaps do - remap(unic,unicode,true) - remap(unicode,unicode,true) - end - end - end +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 - elseif trace_collecting then - report_math("no fallback characters added to font %a",specification.hash) end end + return count end + +interfaces.implement { + name = "mathvariantcode", + public = true, + arguments = { "integer", "integer" }, + actions = { mathematics.variantcode, context }, +} + +interfaces.implement { + name = "mathvariantcount", + public = true, + arguments = "integer", + actions = { mathematics.variantcount, context }, +} |