if not modules then modules = { } end modules ['math-act'] = {
    version   = 1.001,
    comment   = "companion to math-ini.mkiv",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"
}

-- Here we tweak some font properties (if needed). The commented sections
-- have been removed (no longer viable) but can be found in the .lua variant.

-- The tweaks here evolved from experiments with, discussions about and upgrades of
-- the math subsystem, a project that Mikael Sundqvist 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, tostring = type, next, tonumber, tostring
local fastcopy, copytable, insert, remove, concat = table.fastcopy, table.copy, table.insert, table.remove, table.concat
local formatters, gsub, lower = string.formatters, string.gsub, string.lower
local byte = string.byte
local max = math.max
local setmetatableindex, sortedkeys, sortedhash, concat = table.setmetatableindex, table.sortedkeys, table.sortedhash, table.concat
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 abs              = math.abs

local blocks           = characters.blocks
local stepper          = utilities.parsers.stepper

local helpers          = fonts.helpers
local prependcommands  = helpers.prependcommands

local vfcommands       = helpers.commands
local upcommand        = vfcommands.up
local downcommand      = vfcommands.down
local rightcommand     = vfcommands.right
local leftcommand      = vfcommands.left
local slotcommand      = vfcommands.slot
local charcommand      = vfcommands.char
local push             = vfcommands.push
local pop              = vfcommands.pop

local sequencers       = utilities.sequencers
local appendgroup      = sequencers.appendgroup
local appendaction     = sequencers.appendaction

local fontchars        = fonts.hashes.characters
local fontproperties   = fonts.hashes.properties

local mathgaps         = mathematics.gaps

local d_scratchleftoffset  <const> = tex.isdimen("scratchleftoffset")
local d_scratchrightoffset <const> = tex.isdimen("scratchrightoffset")

local use_math_goodies = true   directives.register("math.nogoodies",    function(v) use_math_goodies = not v end)
local checkitalics     = false  trackers  .register("math.checkitalics", function(v) checkitalics     =     v end)

local function registerdone(done,unicode)
    if not trace_tweaking then
        done = true
    elseif done then
        done[unicode] = true
    else
        done = { [unicode] = true }
    end
    return done
end

local mathfontparameteractions = sequencers.new {
    name      = "mathparameters",
    arguments = "target,original",
}

appendgroup("mathparameters","before") -- user
appendgroup("mathparameters","system") -- private
appendgroup("mathparameters","after" ) -- user

function fonts.constructors.assignmathparameters(original,target) -- wrong way around
    local runner = mathfontparameteractions.runner
    if runner then
        runner(original,target)
    end
end

-- we need a better reset because the following will scale

local undefined <const> = 0x3FFFFFFF -- maxdimen or undefined_math_parameter

function mathematics.initializeparameters(target,original,nodimensions)
    local mathparameters = original.mathparameters
    if mathparameters and next(mathparameters) then
        if nodimensions ~= "noscale" then
            mathparameters = mathematics.dimensions(mathparameters)
        end
        --
        -- defaults : times unit or fraction
        --
     -- 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.SpaceAfterScript                  then mathparameters.SpaceAfterScript                  = 0 end
        if not mathparameters.SpaceBeforeScript                 then mathparameters.SpaceBeforeScript                 = mathparameters.SpaceAfterScript end
        if not mathparameters.SpaceBetweenScript                then mathparameters.SpaceBetweenScript                = mathparameters.SpaceAfterScript end
        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                   = mathparameters.SpaceAfterScript end
        if not mathparameters.NoLimitSupFactor                  then mathparameters.NoLimitSupFactor                  = 0 end
        if not mathparameters.NoLimitSubFactor                  then mathparameters.NoLimitSubFactor                  = 0 end
        if not mathparameters.AccentTopShiftUp                  then mathparameters.AccentTopShiftUp                  = 0 end
        if not mathparameters.AccentBottomShiftDown             then mathparameters.AccentBottomShiftDown             = 0 end
        if not mathparameters.FlattenedAccentTopShiftUp         then mathparameters.AccentTopShiftUp                  = 0 end
        if not mathparameters.FlattenedAccentBottomShiftDown    then mathparameters.AccentBottomShiftDown             = 0 end
        if not mathparameters.AccentBaseDepth                   then mathparameters.AccentBaseDepth                   = 0 end
        if not mathparameters.AccentFlattenedBaseDepth          then mathparameters.AccentFlattenedBaseDepth          = 0 end
        if not mathparameters.AccentTopOvershoot                then mathparameters.AccentTopOvershoot                = 0 end
        if not mathparameters.AccentBottomOvershoot             then mathparameters.AccentBottomOvershoot             = 0 end
        if not mathparameters.AccentSuperscriptDrop             then mathparameters.AccentSuperscriptDrop             = 0 end
        if not mathparameters.AccentSuperscriptPercent          then mathparameters.AccentSuperscriptPercent          = 0 end
        if not mathparameters.AccentExtendMargin                then mathparameters.AccentExtendMargin                = 50 end
        if not mathparameters.DelimiterPercent                  then mathparameters.DelimiterPercent                  = 100 end
        if not mathparameters.DelimiterShortfall                then mathparameters.DelimiterShortfall                = 0 end
        if not mathparameters.DelimiterDisplayPercent           then mathparameters.DelimiterDisplayPercent           = 100 end
        if not mathparameters.DelimiterDisplayShortfall         then mathparameters.DelimiterDisplayShortfall         = 0 end
        if not mathparameters.RadicalKernAfterExtensible        then mathparameters.RadicalKernAfterExtensible        = 0 end
        if not mathparameters.RadicalKernBeforeExtensible       then mathparameters.RadicalKernBeforeExtensible       = 0 end
        if not mathparameters.SuperscriptSnap                   then mathparameters.SuperscriptSnap                   = 750 end
        if not mathparameters.SubscriptSnap                     then mathparameters.SubscriptSnap                     = 250 end
        --
-- if mathparameters.RadicalDisplayStyleVerticalGap then
--     mathparameters.RadicalDisplayStyleVerticalGap = mathparameters.RadicalDisplayStyleVerticalGap - 100
-- end
-- if mathparameters.RadicalVerticalGap then
--     mathparameters.RadicalVerticalGap = mathparameters.RadicalVerticalGap - 100
-- end
        --
        target.mathparameters = mathparameters
    end
end

sequencers.appendaction("mathparameters","system","mathematics.initializeparameters")

local how = {
 -- RadicalKernBeforeDegree         = "horizontal",
 -- RadicalKernAfterDegree          = "horizontal",
    ScriptPercentScaleDown          = "unscaled",
    ScriptScriptPercentScaleDown    = "unscaled",
    RadicalDegreeBottomRaisePercent = "unscaled",
    NoLimitSupFactor                = "unscaled",
    NoLimitSubFactor                = "unscaled",
    PrimeRaisePercent               = "unscaled",
    PrimeRaiseComposedPercent       = "unscaled",
    AccentTopOvershoot              = "unscaled",
    AccentBottomOvershoot           = "unscaled",
    AccentSuperscriptPercent        = "unscaled",
    DelimiterPercent                = "unscaled",
    DelimiterDisplayPercent         = "unscaled",
    --
    RadicalRuleThickness            = "vertical",
    OverbarRuleThickness            = "vertical",
    FractionRuleThickness           = "vertical",
    UnderbarRuleThickness           = "vertical",
}

local function scaleparameters(mathparameters,parameters)
    if mathparameters and next(mathparameters) and parameters then
        local factor  = parameters.factor
        local hfactor = parameters.hfactor
        local vfactor = parameters.vfactor
        for name, value in next, mathparameters do
            local h = how[name]
            if h == "unscaled" then
                -- kept
            elseif h == "horizontal" then
                value = value * hfactor
            elseif h == "vertical" then
                value = value * vfactor
            else
                value = value * factor
            end
            mathparameters[name] = value
        end
    end
end

function mathematics.scaleparameters(target,original)
    if not target.properties.math_is_scaled then
        scaleparameters(target.mathparameters,target.parameters)
        target.properties.math_is_scaled = true
    end
end

function mathematics.overloadparameters(target,original)
    if use_math_goodies then
        local mathparameters = target.mathparameters
        if mathparameters and next(mathparameters) then
            local goodies = target.goodies
            if goodies then
                for i=1,#goodies do
                    local goodie = goodies[i]
                    local mathematics = goodie.mathematics
                    if mathematics then
                        local parameters = mathematics.parameters
                        local bigslots   = mathematics.bigslots or mathematics.bigs
                        local scales     = mathematics.scales
                        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
                        if scales then
                            local feature  = scales.feature
                            local features = target.specification.features.normal
                            if feature == nil or features[feature] then
                                target.scriptxscale       = scales.scriptxscale
                                target.scriptyscale       = scales.scriptyscale
                                target.scriptweight       = scales.scriptweight
                                target.scriptscriptxscale = scales.scriptscriptxscale
                                target.scriptscriptyscale = scales.scriptscriptyscale
                                target.scriptscriptweight = scales.scriptscriptweight
                            end
                        end
                    end
                end
            end
        end
    end
end

-- a couple of predefined tweaks:

local datasets       = { }
local mathtweaks     = { datasets = datasets }
mathematics.tweaks   = mathtweaks

-- can be a common helper:

local f_u = formatters["%U"]

local function unicodecharlist(t)
    local r = { }
    local n = 0
    for u in sortedhash(t) do
        n = n + 1 ; r[n] = f_u(u)
    end
    return concat(r," ")
end

local function report_tweak(fmt,target,original,...)
    if fmt then
        local metadata   = (original and original.shared.rawdata.metadata) or
                           (target   and target  .shared.rawdata.metadata)
        local parameters = target.parameters
        if parameters then
            report_mathtweak(
                "%a, size %P, math size %i, %s",
                metadata and metadata.fontname or "unknown",
                parameters.size or 655360,
                parameters.mathsize or 1,
                formatters[fmt](...)
            )
        else
            print("something is wrong")
        end
    else
        report_mathtweak("")
    end
end

local function feedback_tweak(tweak,target,original,done)
    if not done or (type(done) == "table" and not next(done)) then
if trace_tweaking then -- for now
        report_tweak("no need for %a",target,original,tweak)
end
    elseif trace_tweaking then
        report_tweak("tweak %a applied to: %s",target,original,tweak,unicodecharlist(done))
    end
end

mathtweaks.subsets = {
    acenorsuvxz      = { 0x1D44E, 0x1D450, 0x1D452, 0x1D45B, 0x1D45C, 0x1D45F, 0x1D460, 0x1D462, 0x1D463, 0x1D465, 0x1D467 },
    bhklt            = { 0x1D44F, 0x1D455, 0x1D458, 0x1D459, 0x1D461 },
    d                = { 0x1D451 },
    f                = { 0x1D453 },
    gjqy             = { 0x1D454, 0x1D457, 0x1D45E, 0x1D466 },
    i                = { 0x1D456 },
    mw               = { 0x1D45A, 0x1D464 },
    p                = { 0x1D45D },
    letterlike       = { 0x02202 },
    dotless          = { 0x00049, 0x0004A, 0x00131, 0x00237, 0x1D6A4, 0x1D6A5 },
    integrals        = { 0x0222B, 0x0222C, 0x0222D, 0x0222E, 0x0222F, 0x02230, 0x02231, 0x02232, 0x02233, 0x02A0B, 0x02A0C, 0x02A0D, 0x02A0E, 0x02A0F, 0x02A10, 0x02A11, 0x02A12, 0x02A13, 0x02A14, 0x02A15, 0x02A16, 0x02A17, 0x02A18, 0x02A19, 0x02A1A, 0x02A1B, 0x02A1C, 0x02320, 0x02321 },
    horizontalfences = { 0x0203E, 0x023B4, 0x023B5, 0x023DC, 0x023DD, 0x023DE, 0x023DF, 0x023E0, 0x023E1 }, -- not really used
}

local function getalso(target,original)
    local also = target.tweakalso -- maybe
    if not also then
        also = { }
     -- for k, v in sortedhash(target.characters) do
        for k, v in next, target.characters do
            local u = v.unicode
            if u and k ~= u then
                local a = also[u]
                if a then
                    a[#a+1] = k
                else
                    also[u] = { k }
                end
            end
        end
        target.tweakalso = also
    end
    return also
end

-- {
--     tweak = "dimensions",
--     list  = {
--         ["lowercasegreeksansserifbolditalic"] = {
--          -- delta    = 0x003B1 - 0x1D7AA,
--             slant    = -0.2,
--             line     = 0.1,
--             mode     = 1,
--             width    = 0.675,
--          -- scale    = 0.975,
--             squeeze  = 0.975,
--             extend   = .7,
--         },
--     },
-- },

 -- ["0x7C.variants.*"] = { squeeze = 0.10, height = 0.10, depth = 0.10 },

local detail  do

    local splitter = lpeg.tsplitat(".")
    local private  = fonts.helpers.privateslot

    detail = function(characters,k)
        if type(k) == "string" then
            local t = lpegmatch(splitter,k)
            local n = #t
            if n > 0 then
                local slot = t[1]
                local base = tonumber(slot) or tonumber(slot,16) or private(slot)
                if base then
                    local c = characters[base]
                    if c and n > 1 then
                        local list = t[2]
                        if list == "parts" then
                            local nxt = c.next
                            while nxt do
                                c = characters[nxt]
                                nxt = c.next
                            end
                            c = c.parts
                            if c then
                                local index = t[3]
                                if index == "*" then
                                    return t
                                else
                                    if index == "top" then
                                        index = #c
                                    elseif index == "bottom" then
                                        index = 1
                                    else
                                        index = tonumber(index)
                                    end
                                    if index then
                                        c = c[index]
                                        if c then
                                            return c.glyph
                                        end
                                    end
                                end
                            end
                        elseif list == "variants" then
                            local index = t[3]
                            if index == "*" then
                                local t = { }
                                local nxt = c.next
                                while nxt do
                                    t[#t+1] = nxt
                                    c = characters[nxt]
                                    nxt = c.next
                                end
                                return t
                            else
                                index = tonumber(index)
                                if index then
                                    local nxt = c.next
                                    while nxt and index > 1 do
                                        c = characters[nxt]
                                        nxt = c.next
                                        index = index - 1
                                    end
                                    return nxt
                                end
                            end
                        elseif list == "flataccent" then
                            return c.flataccent
                        end
                    end
                end
            end
        else
            return k
        end
    end

end

-- This temporary tweak was used when we (MS & HH) were fixing the Latin Modern
-- parameters that relate to script placement. We started from the original cmr
-- ratios combined with the formal specification and ended up with the following
-- values. In the end we rejected this tweak and setteled for checking and fixing:
--
-- SubscriptShiftDown
-- SubscriptShiftDownWithSuperscript
-- SuperscriptShiftUp
-- SuperscriptShiftUpCramped
--
-- because it looks a bit arbitrary what values are set. We keep the code below
-- as documentation.

-- do
--     -- modern
--     --
--     -- local factors = {
--     --     scripts = {
--     --         SubscriptBaselineDropMin          = 0.116,
--     --         SubscriptShiftDown                = 0.348,
--     --         SubscriptShiftDownWithSuperscript = 0.573,
--     --         SubscriptTopMax                   = 0.800,
--     --         SuperscriptBaselineDropMax        = 0.896,
--     --         SuperscriptBottomMaxWithSubscript = 0.800,
--     --         SuperscriptBottomMin              = 0.250,
--     --         SuperscriptShiftUp                = 0.958,
--     --         SuperscriptShiftUpCramped         = 0.958,
--     --     }
--     -- }
--
--     -- -- cambria
--     --
--     -- local factors = {
--     --     scripts = {
--     --         SubscriptBaselineDropMin          = 0.279,
--     --         SubscriptShiftDown                = 0.364,
--     --         SubscriptShiftDownWithSuperscript = 0.547,
--     --         SubscriptTopMax                   = 0.662,
--     --         SuperscriptBaselineDropMax        = 0.401,
--     --         SuperscriptBottomMaxWithSubscript = 0.667,
--     --         SuperscriptBottomMin              = 0.208,
--     --         SuperscriptShiftUp                = 0.654,
--     --         SuperscriptShiftUpCramped         = 0.654,
--     --     }
--     -- }
--
--     -- after some tests and inspection
--     --
--
--     local factors = {
--         scripts = {
--             SubscriptBaselineDropMin          = 0.100, -- harmless but small (seldom triggered)
--             SubscriptShiftDown                = 0.400, -- by inspection in several files
--             SubscriptShiftDownWithSuperscript = 0.400, -- as above
--             SubscriptTopMax                   = 0.800, -- Microsoft recommendation
--             SuperscriptBaselineDropMax        = 0.100, -- see SubscriptBaselineDropMin
--             SuperscriptBottomMaxWithSubscript = 0.800, -- Microsoft recommendation
--             SuperscriptBottomMin              = 0.250, -- Microsoft recommendation
--             SuperscriptShiftUp                = 0.650, -- by inspection, but also a bit gamble
--             SuperscriptShiftUpCramped         = 0.650, -- see above, non-TeX
--         }
--     }
--
--     datasets.fixparameters = factors
--
--     function mathtweaks.fixparameters(target,original,parameters)
--         local mathparameters = target.mathparameters
--         if mathparameters and next(mathparameters) then
--             local xheight = target.parameters.xheight
--             -- todo : options
--             for k, v in next, factors.scripts do
--                 mathparameters[k] = v * xheight
--             end
--         end
--     end
--
-- end

do

    local stepper     = utilities.parsers.stepper
    local count       = 0
    local toeffect    = fonts.toeffect
    local privateslot = fonts.helpers.privateslot

    local function adapt(list,target,original,targetcharacters,originalcharacters,k,v,compact,n)
        k = mathgaps[k] or k
        local character = targetcharacters[k]
        if character then
         -- if not character.tweaked then -- todo: add a force
                local t = type(v)
                if t == "number" then
                    v = list[v]
                    t = type(v)
                end
                if t == "table" and next(v) then

                    local axis = tonumber(v.axis)
                    if axis then
                        axis = target.mathparameters.AxisHeight * axis
                    end

                    local factor = v.factor
                    if factor then
                        local m = v
                        v = setmetatableindex({
                            width   = factor,
                            height  = factor,
                            depth   = factor,
                            squeeze = factor,
                            extend  = factor,
                        }, v)
                    end
                    local originalslot = v.original
                    if not originalslot then
                        local delta = v.delta
                        if delta then
                            originalslot = k + delta
                        end
                    end
                    if originalslot then
                        originalslot = mathgaps[originalslot] or originalslot
                        local data = targetcharacters[originalslot]
                        if data then
                            data = copytable(data)
                            data.unicode = originalslot
                            targetcharacters[k] = data
                            character = data
                        else
                            report_mathtweak("no slot %U",originalslot)
                            return
                        end
                    end
                    --
                    local width        = character.width
                    local height       = character.height
                    local depth        = character.depth
                    local italic       = character.italic
                    local topanchor    = character.topanchor
                    local bottomanchor = character.bottomanchor
                    --
                    local widthfactor   = v.width
                    local heightfactor  = v.height
                    local depthfactor   = v.depth
                    local italicfactor  = v.italic
                    local anchorfactor  = v.anchor
                    local advancefactor = v.advance
                    local xoffsetfactor = v.xoffset
                    local yoffsetfactor = v.yoffset
                    local scalefactor   = v.scale
                    local total = (height or 0) + (depth or 0)
                    if scalefactor ~= 1 then
                        character.scale = scalefactor
                    end
                    if width and width ~= 0 then
                        if advancefactor then
                            character.advance = advancefactor * width
                        else
                            character.advance = character.advance or width -- so advance is oldwidth
                        end
                        if widthfactor then
                            character.width = widthfactor * width
                        end
                        if xoffsetfactor then
                            character.xoffset = xoffsetfactor * width
                        end
                    end
                    if height and height ~= 0 then
                        if heightfactor then
                            character.height  = heightfactor * height
                        end
                    end
                    if depth and depthfactor then
                        character.depth = depthfactor * depth
                    end
                    if yoffsetfactor then
                        character.yoffset = yoffsetfactor * total
                    end

                    if axis then
                        character.height  = (character.height  or 0) - axis
                        character.depth   = (character.depth   or 0) + axis
                        character.yoffset = (character.yoffset or 0) + axis
                    end

                    if italicfactor then
                        if italic then
                            character.italic = italicfactor * italic
                        elseif width and italicfactor ~= 1 then
                            character.italic = italicfactor * width
                        end
                    end
                    if anchorfactor then
                        character.topanchor = anchorfactor * (topanchor or width)
                    end
                 -- if anchorfactor then
                 --     character.bottomaccent = anchorfactor * (bottomanchor or width)
                 -- end
                 -- begin experiment
                    local line = v.wline
                    if line then
                        local parameters = target.parameters
                        v.line = parameters.hfactor * line / parameters.units
                    end
                 -- end experiment
                    character.effect = toeffect(v) -- todo: move wline test inside here
                 -- begin experiment
                    v.line = line
                 -- end experiment
                    if trace_tweaking then
                        report_tweak("adapting dimensions of %U ",target,original,k)
                    end
                     -- missing when private
                    local originaldata = originalcharacters[k] -- or targetcharacters[k]
                    local smaller = originaldata and originaldata.smaller
                    if compact and smaller and smaller ~= k then
                        adapt(list,target,original,targetcharacters,originalcharacters,smaller,v,compact,n+1)
                    end
                    count = count + 1
                else
                    report_mathtweak("invalid dimension entry %U",k)
                end
             -- character.tweaked = true
                if v.all then
                    local nxt = character.next
                    if nxt then
                        adapt(list,target,original,targetcharacters,originalcharacters,nxt,v,compact,n)
                    else
                        local parts = character.parts
                        if parts then
                            for i=1,#parts do
                                adapt(list,target,original,targetcharacters,originalcharacters,parts[i],v,compact,n)
                            end
                        end
                    end
                end
         -- end
        else
            report_tweak("no character %U",target,original,k)
        end
    end

    function mathtweaks.dimensions(target,original,parameters)
        local list = parameters.list
        if list then
            local targetcharacters   = target.characters
            local originalcharacters = original.characters
            local compact = target.parameters.textscale and true or false
            count = 0
            for k, v in sortedhash(list) do
                local t = type(k)
                if t == "number" then
                    adapt(list,target,original,targetcharacters,originalcharacters,k,v,compact,1)
                elseif t == "string" then
                    local d = privateslot(k) or detail(targetcharacters,k) -- watch the private here
                    local t = type(d)
                    if t == "table" then
                        for i=1,#d do
                            adapt(list,target,original,targetcharacters,originalcharacters,d[i],v,compact,1)
                        end
                    elseif t == "number" then
                        adapt(list,target,original,targetcharacters,originalcharacters,d,v,compact,1)
                    elseif d then
                        -- some kind of error
                    else
                        local r = blocks[k]
                        if r then
                            local done = false
                            for i=r.first,r.last do
                                adapt(list,target,original,targetcharacters,originalcharacters,i,v,compact,1)
                            end
                        else
                            stepper(k,function(n)
                                adapt(list,target,original,targetcharacters,originalcharacters,n,v,compact,1)
                            end)
                        end
                    end
             -- elseif t == "table" then
             --     for i=1,#t do
             --         adapt(list,target,original,targetcharacters,originalcharacters,t[i],v,compact,1)
             --     end
                end
            end
            if trace_tweaking and count > 0 then
                report_mathtweak("%i dimensions adapted",count)
            end
        end
    end

end

do

    function mathtweaks.message(target,original,parameters)
        report_mathtweak(parameters.text or "no message")
    end

    function mathtweaks.showinfo(target,original,parameters)
        local mathparameters = target.mathparameters
        for k, v in sortedhash(mathparameters) do
            report_mathtweak("%s : %s",k,v)
        end
    end

end

do

    function mathtweaks.wipevariants(target,original,parameters)
        local list = parameters.list
        if list then
            local targetcharacters   = target.characters
         -- local originalcharacters = original.characters
            local count = 0
         -- local also  = getalso(target,original)
            local done  = false
            for k, v in sortedhash(list) do
                local ori = targetcharacters[k]
                local nxt = ori.next
                local cnt = v
                if nxt then
                    local prt = nil
                    local pro = nil
                    local lst = { }
                    while nxt do
                        local chr = targetcharacters[nxt]
                        lst[#lst+1] = chr
                        nxt = chr.next
                        if not nxt then
                            prt = chr.parts
                            pro = chr.partsorientation
                            break
                        end
                    end
                    if prt then
                        count = count + 1
                        if cnt ~= "*" then
                            if #lst < cnt then
                                cnt = #lst
                            end
                            ori = lst[cnt]
                        end
                        ori.parts = prt
                        ori.partsorientation = pro
                    end
                    done = registerdone(done,k)
                end
            end
            feedback_tweak("wipevariants",target,original,done)
        end
    end

end

do

    -- This is a horrible tweak for lm that has bars inconsistent with other fences.

    local function find(targetcharacters,slot,n)
        local chr = targetcharacters[slot]
        while chr do
            slot = chr.next
            if not slot then
                break
            elseif n == 1 then
                return slot
            end
            n = n - 1
            chr = targetcharacters[slot]
        end
        return nil
    end

    function mathtweaks.fixvariants(target,original,parameters)
        local list = parameters.list
        if list then
            local targetcharacters   = target.characters
            local done  = false
            for k, v in sortedhash(list) do
                local tmp = v.template
                local idx = v.index
                local dy  = v.yoffset or 0
                if tmp and idx then
                    local olduni = find(targetcharacters,k,idx)
                    local tmpuni = find(targetcharacters,tmp,idx)
                    local axis   = target.mathparameters.AxisHeight
                    if axis then
                        while olduni and tmpuni do
                            local oldchr  = targetcharacters[olduni]
                            local tmpchr  = targetcharacters[tmpuni]
                            local oldht   = oldchr.height or 0
                            local olddp   = oldchr.depth  or 0
                            local tmpht   = tmpchr.height or 0
                            local tmpdp   = tmpchr.depth  or 0
                            local scale   = (tmpht + tmpdp) / (oldht + olddp)
                            local total   = (oldht - olddp) * scale
                            local yoffset = axis - total/2
                            oldchr.effect = { squeeze = scale }
                            oldchr.height  = scale * oldht + yoffset
                            oldchr.depth   = scale * olddp - yoffset
                            oldchr.yoffset = yoffset
                            olduni = oldchr.next
                            tmpuni = tmpchr.next
                        end
                        done = registerdone(done,k)
                    end
                end
            end
            feedback_tweak("addvariants",target,original,done)
        end
    end

end

do

    function mathtweaks.replace(target,original,parameters)
        local list = parameters.list
        if list then
            local targetcharacters   = target.characters
            local originalcharacters = original.characters
            local unicodes = original.resources.unicodes
            if unicodes then
                local count = 0
                for k, v in sortedhash(list) do
                    if type(v) == "string" then
                        v = unicodes[v]
                    end
                    if type(v) == "number" then
                        targetcharacters[mathgaps[k] or k] = targetcharacters[mathgaps[v] or v]
                        count = count + 1
                    end
                end
                if trace_tweaking and count > 0 then
                    report_tweak("%i permanent replacements",target,original,count)
                end
            end
        end
    end

    function mathtweaks.substitute(target,original,parameters)
        local list = parameters.list
        if list then
            local targetcharacters   = target.characters
            local originalcharacters = original.characters
            local getsubstitution    = fonts.handlers.otf.getsubstitution
            local count              = 0
            for k, v in next, list do -- no need for sortedhash(list) unless we report
                local sub = getsubstitution(original,k,v,true)
                if sub then
                    targetcharacters[mathgaps[k] or k] = targetcharacters[mathgaps[sub] or sub]
                    count = count + 1
                end
            end
            if trace_tweaking and count > 0 then
                report_tweak("%i permanent substitutions",target,original,count)
            end
        end
    end

end

do

    -- maybe we'll have a different name

    function mathtweaks.kernpairs(target,original,parameters)
        local list = parameters.list
        if list then
            local targetcharacters   = target.characters
            local originalcharacters = original.characters
            local done               = false

            local function add(v,n)
                local chardata = targetcharacters[mathgaps[n] or n]
                if chardata then
                    local width = chardata.width
                    if width then
                        local kerns = chardata.kerns or { }
                        for kk, vv in next, v do
                            stepper(kk,function(nn) -- todo: also make stepper accept a table
                                local t = mathgaps[nn] or nn
                                if t then
                                    kerns[t] = vv * width
                                    done = registerdone(done,t)
                                end
                            end)
                        end
                        chardata.kerns = kerns
                    end
                end
            end

            for k, v in next, list do -- no need for sortedhash(list) unless we report
                stepper(k,function(n) -- todo: also make stepper accept a table
                    add(v,n)
                end)
            end

         -- for k, v in next, list do -- no need for sortedhash(list) unless we report
         --     local chardata = targetcharacters[mathgaps[k] or k]
         --     if chardata then
         --         local width = chardata.width
         --         if width then
         --             local kerns = chardata.kerns or { }
         --             for kk, vv in next, v do
         --                 local t = mathgaps[kk] or kk
         --                 if t then
         --                     kerns[t] = vv * width
         --                     count = count + 1
         --                 end
         --             end
         --             chardata.kerns = kerns
         --         end
         --     end
         -- end

            feedback_tweak("kernpairs",target,original,done)
        end
    end

end

do

    local list = {
        { 0x2032, 1 },
        { 0x2033, 2, 0x2032 },
        { 0x2034, 3, 0x2032 },
        { 0x2057, 4, 0x2032 },
        { 0x2035, 1 },
        { 0x2036, 2, 0x2035 },
        { 0x2037, 3, 0x2035 },
    }

    datasets.fixprimes = list

    function mathtweaks.fixprimes(target,original,parameters)
        local targetcharacters = target.characters
        local factor = parameters.factor or 1
        local fake   = tonumber(parameters.fake)
        for i=1,#list do
            local entry   = list[i]
            local unicode = entry[1]
            local count   = entry[2]
            local used    = fonts.handlers.otf.getsubstitution(target,unicode,"ssty",true,"math","dflt") or unicode
            local data    = targetcharacters[used]
            if data then
                targetcharacters[unicode] = data
                local oldheight = data.height or 0
                local newheight = factor * oldheight
                data.yoffset = newheight - (oldheight or 0)
                data.height  = newheight
                data.smaller = nil
            elseif not fake then
                report_tweak("missing %i prime %U",target,original,count,unicode)
            end
        end
        if fake then
            for i=1,#list do
                local entry = list[i]
                local count = entry[2]
                if count > 1 then
                    local unicode  = entry[1]
                    local original = entry[3]
                    local data     = targetcharacters[original]
                    if data then
                        local oldwidth = data.width
                        local xoffset  = fake * oldwidth
                        local newwidth = oldwidth + (count - 1) * xoffset
                        targetcharacters[unicode] = {
                            width    = newwidth,
                            height   = data.height,
                            unicode  = unicode,
                            commands = {
                                              { "offset",           0, 0, original },
                                              { "offset",     xoffset, 0, original },
                                count > 2 and { "offset", 2 * xoffset, 0, original } or nil,
                                count > 3 and { "offset", 3 * xoffset, 0, original } or nil,
                            },
                        }
                    end
                end
            end
        end
    end

end

do

    local nps = fonts.helpers.newprivateslot

    local privates = {
        [0x2212] = nps("unary minus"),
        [0x002B] = nps("unary plus"),
        [0x00B1] = nps("unary plus minus"),
        [0x2213] = nps("unary minus plus"),
    }

    -- these are the values tested with texgyre-bonum

    local predefined  = {
        ["unary minus"] = {
            original = 0x2212,
            extend   = .5,
            width    = .5,
            unicode  = 0x002D, -- hyphen minus
        },
        ["unary plus"] = {
            original = 0x002B,
            extend   = .5,
            squeeze  = .5,
            width    = .5,
            height   = .5,
            yoffset  = .2,
            mode     = 2,
            wline    = .5,
            unicode  = 0x002B,
        },
        ["unary plus minus"] = {
            original = 0x00B1,
            extend   = .5,
            squeeze  = .5,
            width    = .5,
            height   = .5,
            yoffset  = .2,
            mode     = 2,
            wline    = .5,
        },
        ["unary minus plus"] = {
            original = 0x2213,
            extend   = .5,
            squeeze  = .5,
            width    = .5,
            height   = .5,
            yoffset  = .2,
            mode     = 2,
            wline    = .5,
        },
    }

 -- {
 --     tweak = "addprivates",
 --     list  = {
 --         -- for specific parameters see act file
 --         ["unary minus"]      = { preset = "unary minus"      },
 --         ["unary plus"]       = { preset = "unary plus"       },
 --         ["unary plus minus"] = { preset = "unary plus minus" },
 --         ["unary minus plus"] = { preset = "unary minus plus" },
 --     },
 -- },

    function mathtweaks.addprivates(target,original,parameters)
        local list = parameters.list or predefined
        if list then
            local targetcharacters   = target.characters
            local targetparameters   = target.parameters
            local originalcharacters = original.characters
            local processedprivates  = { }
            for name, v in sortedhash(list) do
                if type(v) == "table" then
                    local preset = v.preset
                    if preset then
                        local p = predefined[preset]
                        if p then
                            -- we replace preset = name by the table bound to name
                            v = table.combine(p,v)
                            p.preset = nil
                        else
                            goto next
                        end
                    end
                    local charslot = v.original
                    if charslot then
                        local chardata = targetcharacters[charslot]
                        if chardata then
                            local clonedata = copytable(chardata)
                            local cloneslot = nps(name)
                            local unicode   = v.unicode or clonedata.unicode
                            clonedata.uncode = unicode
                            targetcharacters[cloneslot] = clonedata
                            if trace_tweaking then
                                report_tweak("cloning %a from %C into %U with tounicode %U",target,original,name,charslot,cloneslot,unicode)
                            end
                        end
                        processedprivates[name] = v
                    end
                  ::next::
                end
            end
            mathtweaks.dimensions(target,original,{
                tweak = parameters.tweak,
                list  = processedprivates,
            })
        end
    end

end

-- do
--
--     function mathtweaks.fixanchors(target,original,parameters)
--         local targetcharacters= target.characters
--         local factor = tonumber(parameters.factor) or 0
--         if factor ~= 0 then
--             local done = false
--             for k, v in next, targetcharacters do
--                 local a = v.topanchor
--                 if a and a > 0 then
--                     v.topanchor = a * factor
--                     count = count + 1
--                     done = registerdone(done,u)
--                 end
--             end
--         end
--         feedback_tweak("fixanchors",target,original,done)
--     end
--
-- end

-- do
--
--     -- actually this should be a an engine feature driven by category because we don't
--     -- want this in display mode .. only a test for MS and HH
--
--     local issymbol = characters.is_symbol
--
--     function mathtweaks.oldstylemath(target,original,parameters)
--         local chardata = characters.data
--         local characters = target.characters
--         local axis       = target.mathparameters.AxisHeight
--         local delta      = (parameters.factor or .1) * axis
--         target.mathparameters.AxisHeight = (axis - delta)
--         for k, v in sortedhash(characters) do
--             if issymbol[k] then -- quick hack, engine knows
--                 v.yoffset = -delta
--                 v.height  = (v.height or 0) - delta
--                 v.depth   = (v.depth  or 0) - delta
--             end
--         end
--     end
--
--     function mathtweaks.oldstylemath(target,original,parameters)
--         -- not relevant
--     end
--
-- end

do

    function mathtweaks.simplifykerns(target,original,parameters)
        local characters = target.characters
        local done       = false
     -- for u, v in sortedhash(characters) do
        for u, v in next, characters do
            local mathkerns = v.mathkerns
            if mathkerns then
                local k = mathkerns.topleft
                if k then
                    k = k[#k].kern
                    if k then
                        v.topleft = k
                    end
                end
                local k = mathkerns.topright
                if k then
                    k = k[#k].kern
                    if k then
                        v.topright = k
                    end
                end
                local k = mathkerns.bottomleft
                if k then
                    k = k[1].kern -- todo get value at baseline
                    if k then
                        v.bottomleft = k
                    end
                end
                local k = mathkerns.bottomright
                if k then
                    k = k[1].kern -- todo get value at baseline
                    if k then
                        v.bottomright = k
                    end
                end
                v.mathkerns = nil
                done = registerdone(done,u)
            end
        end
        feedback_tweak("simplifykerns",target,original,done)
    end

end


-- In TeX The Program we find in section 543 a remark about the second use of math
-- italic: it is always added to the width except with respect to the position of
-- the subscript. That paragraph also mentions that the number of different widths
-- is normally small so they can be shared (there is an eight bit index into a width
-- array). Actually the limits of at most 16 heights and depths has as side effect
-- that we get some snapping of sizes. Now, because many math characters have an
-- italic correction, the saving on shared widths is negated by the amount of (at
-- most 64) italics. So in practice there is no gain and the italic correction could
-- have served as bottom kern. We think that the following approach (that we actualy
-- came to by a different reasonsing: inconsistent open type fonts) is quite valid
-- and robust. It's just that the opentype math fonts should never have gone that
-- TeX italic route. Italic usage is more clear from section 543 than from the math
-- rendering code.

do

     local function wipe(whatever,target,original,parameters,field,move,integrals)
        local targetcharacters   = target.characters
        local targetdescriptions = target.descriptions
        local factor             = target.parameters.factor
        local correct            = parameters.correct
        local done               = false
        local function getllx(u)
            local d = targetdescriptions[u]
            if d then
                local b = d.boundingbox
                if b then
                    local llx = b[1]
                    if llx < 0 then
                        return - llx
                    end
                end
            end
            return false
        end
        local function step(s)
            while s do
                local u = mathgaps[s] or s
                local c = targetcharacters[u]
                if c then
                    if field == "topanchor" then
                        if c.topanchor then
                            c.topanchor = nil
                        else
                            goto smaller
                        end
                    else
                        local okay   = false
                        local italic = c.italic
                        if move and not c.advance then -- advance check prevents double move
                            local width  = c.width or 0
                            c.advance = width
                            if correct then
                                local llx = getllx(u)
                                if llx then
                                    local topanchor = c.topanchor
                                    llx   = llx * factor
                                    width = width + llx
                                    c.xoffset = llx
                                    if topanchor then
                                        c.topanchor = topanchor + llx
                                    end
                                    -- too bad (schola e^x):
                                 -- c.bottomleft = (c.bottomleft or 0) - llx
                                 -- c.topleft    = (c.topleft    or 0) - llx
                                    okay = true
                                end
                            end
                            if italic and italic ~= 0 then
                                c.width       = width + italic
                                c.bottomright = - italic
                                okay = true
                            else
                                c.width = width
                            end
                            c.bottomanchor = width/2 -- maybe optional
                        end
                        if italic then
                            c.italic = nil
                            okay = true
                        end
                        if okay then
                            done = registerdone(done,u)
                        else
                            goto smaller
                        end
                    end
                    goto smaller
                  ::smaller::
                    s = c.smaller
                  ::variants::
                    -- no italics here anyway but we could check them some day
                else
                    break
                end
            end
        end
        local list = parameters.list -- todo: ranges
        if list == "letters" or parameters.letters then
            local chardata = characters.data
         -- for k, v in sortedhash(targetcharacters) do
            for k, v in next, targetcharacters do
                if v.italic then
                    local d = chardata[v.unicode]
                    local c = d and d.category
                    if c == "ll" or c == "lu" then
                        step(k)
                    end
                end
            end
            goto done
        end
        if not list or list == "all" or list == true or parameters.all then
            list = sortedkeys(targetcharacters)
        elseif type(list) == "string" then
            list = { list }
        end
        for i=1,#list do
            local l = list[i]
            local t = type(l)
            if not l then
                -- can be false
            elseif t == "table" then
                for i=1,#l do
                    step(l[i])
                end
            elseif t == "number" then
                step(l)
            else
                local r = blocks[l]
                if r then
                    for i=r.first,r.last do
                        step(i)
                    end
                else
                    stepper(l,step)
                end
            end
        end
      ::done::
        feedback_tweak(whatever,target,original,done)
    end

    function mathtweaks.wipeanchors(target,original,parameters)
        wipe("wipeanchors",target,original,parameters,"topanchor")
    end

    function mathtweaks.wipeitalics(target,original,parameters)
        if not checkitalics then
            wipe("wipeitalics",target,original,parameters,"italic")
        end
    end

    function mathtweaks.moveitalics(target,original,parameters)
        wipe("moveitalics",target,original,parameters,"italic",true)
    end

 -- function mathtweaks.fixdigits(target,original,parameters)
 --     mathtweaks.fixanchors(target,original,{ list = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 } })
 -- end

end

do

    function mathtweaks.topanchors(target,original,parameters)
        local characters = target.characters
        local list       = parameters.list
        if list then
            local done = false
            for u, v in sortedhash(list) do
                local c = characters[k]
                if c then
                    local w = c.width
                    if w and w ~= 0 then
                        c.topanchor = v * w
                        done = registerdone(done,u)
                    end
                end
            end
            feedback_tweak("topanchors",target,original,done)
        end
    end

    function mathtweaks.movelimits(target,original,parameters)
        local characters = target.characters
        local list       = parameters.list
        if list then
            local factor = parameters.factor or 1
            local also   = getalso(target,original)
            local done   = { }
            local function relocate(u,factor)
                if done[u] then
                    return
                end
                done[u] = true
                local c = characters[u]
                if c then
                    local italic = c.italic
                    if italic then
                        if italic ~= 0 then
                            local width = c.width or 0
                            local half  = (italic/2) * factor
                            c.topanchor    = width/2 + half
                            c.bottomanchor = width/2 - half
                            c.bottomright  = - italic * (parameters.icfactor or 1)
                            if trace_tweaking then
                                -- todo
                            end
                        end
                        c.anchored     = true
                        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/2 + half
                            bchar.bottomanchor = width/2 - half
                            bchar.bottomright  = - italic
                            if trace_tweaking then
                                -- todo
                            end
                            tchar.italic   = nil
                            bchar.italic   = nil
                            tchar.anchored = true
                            bchar.anchored = true
                        end
                        c.vitalic = nil
                    end
                    if also then
                        local a = also[u]
                        if a then
                            for i=1,#a do
                                relocate(a[i],factor)
                            end
                        end
                    end
                end
            end
            if #list > 0 then
                for i=1,#list do
                    relocate(list[i],factor)
                end
            else
                for k, v in sortedhash(list) do
                    relocate(k,tonumber(v) or factor)
                end
            end
            feedback_tweak("movelimits",target,original,done)
        end
    end

end

do

    -- musical timestamp: March 2022, Antonio Sanches (Bad Hombre), live performance in NL

    function mathtweaks.kerns(target,original,parameters)
        local kerns = parameters.list
        if kerns then
            local characters = target.characters
            local done       = false
            local function setone(uc,data)
                local function set(unicode)
                    unicode = mathgaps[unicode] or unicode
                    local chardata = characters[unicode]
                    if chardata then
                        local width = chardata.width  or 0
                        local k = data.topleft     ; if k and k ~= 0 then chardata.topleft     = k * width end
                        local k = data.topright    ; if k and k ~= 0 then chardata.topright    = k * width end
                        local k = data.bottomleft  ; if k and k ~= 0 then chardata.bottomleft  = k * width end
                        local k = data.bottomright ; if k and k ~= 0 then chardata.bottomright = k * width end
                        done = registerdone(done,unicode)
                    end
                end
                local unicode  = detail(characters,uc)
                if type(unicode) == "table" then
                    for i=1,#unicode do
                        set(unicode[i])
                    end
                elseif unicode then
                    set(unicode)
                end
            end
            for unicode, data in next, kerns do
                setone(unicode,data) -- withscriptcode(tfmdata,unicode,data,kernone)
                -- also smaller
            end
            feedback_tweak("kerns",target,original,done)
        end
    end

end

do

    function mathtweaks.margins(target,original,parameters)
        local kerns = parameters.list
        if kerns then
            local characters = target.characters
            local done       = false
            local function setone(uc,data)
                local function set(unicode)
                    unicode = mathgaps[unicode] or unicode
                    local chardata = characters[unicode]
                    if chardata then
                        local width = chardata.width  or 0
                        local k = data.left   ; if k and k ~= 0 then chardata.leftmargin   = k * width end
                        local k = data.right  ; if k and k ~= 0 then chardata.rightmargin  = k * width end
                        local k = data.top    ; if k and k ~= 0 then chardata.topmargin    = k * width end
                        local k = data.bottom ; if k and k ~= 0 then chardata.bottommargin = k * width end
                        done = registerdone(done,unicode)
                    end
                end
                local unicode  = detail(characters,uc)
                if type(unicode) == "table" then
                    for i=1,#unicode do
                        set(unicode[i])
                    end
                elseif unicode then
                    set(unicode)
                end
            end
            for unicode, data in next, kerns do
                setone(unicode,data) -- withscriptcode(tfmdata,unicode,data,kernone)
                -- also smaller
            end
            feedback_tweak("margins",target,original,done)
        end
    end


end

do

    -- musical timestamp: June 2022, Porcupine Tree - Rats Return

    -- we can actually share these and flag them as being tweaked

    local function scale(t,width,total)
        local r = { }
        for i=1,#t do
            local ti = t[i]
            local kern   = ti.kern
            local height = ti.height
            if kern then
                kern = width * kern
            end
            if height then
                height = total * height
            end
            r[i] = {
                kern   = kern or 0,
                height = height or 0,
            }
        end
        return r
    end

    function mathtweaks.staircase(target,original,parameters)
        local kerns = parameters.list
        if kerns then
            local characters = target.characters
            local function kernone(unicode,data)
                local chardata = characters[mathgaps[unicode] or unicode]
                local total = (chardata.height or 0) + (chardata.depth or 0)
                local width = chardata.width or 0
                if data then
                    local tl = data.topleft     ; if tl then tl = scale(tl,width,total) end
                    local tr = data.topright    ; if tr then tr = scale(tr,width,total) end
                    local bl = data.bottomleft  ; if bl then bl = scale(bl,width,total) end
                    local br = data.bottomright ; if br then br = scale(br,width,total) end
                    chardata.mathkerns = {
                        topleft     = tl,
                        ropright    = tr,
                        bottomleft  = bl,
                        bottomright = br,
                    }
                else
                    chardata.mathkerns = nil
                end
            end
            for unicode, data in next, kerns do
                kernone(unicode,data) -- withscriptcode(tfmdata,unicode,data,kernone)
                -- also smaller
            end
        end
    end

end

do

 -- local list = {
 --     [0x203E] = { factor = .4 }, -- overbar
 --     [0x203E] = { factor = .7 }, -- underbar
 --     [0x23DE] = { factor = .4 }, -- overbrace
 --     [0x23DF] = { factor = .7 }, -- underbrace
 --     [0x23DC] = { factor = .4 }, -- overparent
 --     [0x23DD] = { factor = .7 }, -- underparent
 --     [0x23B4] = { factor = .4 }, -- overbracket
 --     [0x23B5] = { factor = .7 }, -- underbracket
 -- }

    -- We can patch the dimensions in-place or we can use additional characters in
    -- the private namespace.

    -- local addprivate = fonts.helpers.addprivate
    -- local newnextglyph = addprivate(target,formatters["M-N-%H"](nextglyph),newnextdata)

    local nps = fonts.helpers.newprivateslot

    local umbracepiece = nps("um brace piece") -- will be created
    local lmbracepiece = nps("lm brace piece") -- will be created
    local cmbracepiece = nps("cm brace piece") -- will be created : center piece for brace builder hack

    local ulbracepiece = nps("ul brace piece")
    local urbracepiece = nps("ur brace piece")
    local llbracepiece = nps("ll brace piece")
    local lrbracepiece = nps("lr brace piece")

    local over  = { factor = "over"   }
    local under = { factor = "under"  }

    local candidates = {
        over = {
            [0x203E] = over,  -- overbar
            [0x23DE] = over,  -- overbrace
            [0x23DC] = over,  -- overparent
            [0x23B4] = over,  -- overbracket
        },
        under = {
            [0x23DF] = under, -- underbrace
            [0x23DD] = under, -- underparent
            [0x23B5] = under, -- underbracket
        },
        accent = {
            [0x0300] = over,  -- widegrave
            [0x0308] = over,  -- wideddot
            [0x0304] = over,  -- macron (bar)
            [0x0305] = over,  -- widebar
            [0x0301] = over,  -- wideacute
            [0x0302] = over,  -- widehat
            [0x030C] = over,  -- widecheck
            [0x0306] = over,  -- widebreve
            [0x0307] = over,  -- widedot
            [0x030A] = over,  -- widering
            [0x0303] = over,  -- widetilde
            [0x20DB] = over,  -- widedddot
        },
    }

    datasets.accentdimensions = candidates

    local function adapt(c,factor,baseheight,basedepth)
        if not c.tweaked then
            local height  = c.height or 0
            local depth   = c.depth  or 0
            local yoffset = 0
            if factor == "over" then
                local h = height - baseheight
                yoffset = h - height
                height  = h
                depth   = depth - baseheight
            elseif factor == "under" then
                local d = depth - basedepth
                yoffset = depth - d
                depth   = d
                height  = height - baseheight
            elseif height > 0 then
                local h = tonumber(factor) * height
                yoffset = h - height
                height  = h
            elseif depth > 0 then
                local d = tonumber(factor) * depth
                yoffset = depth - d
                depth   = d
            end
            c.yoffset = yoffset ~= 0 and yoffset or nil
            c.height  = height   > 0 and height  or nil
            c.depth   = depth    > 0 and depth   or nil
            c.tweaked = true
        end
    end

    local function process(target,original,characters,list,baseheight,basedepth)
        if list then
            for k, v in sortedhash(list) do -- sort for tracing
                local c = characters[k]
                if c and not c.yoffset then
                    local factor = v.factor
                    if factor then
                        adapt(c,factor,baseheight,basedepth)
                        local nc  = c.next
                        local nv = 0
                        local ns = 0
                        while nc do
                            local c = characters[nc]
                            if c then
                                adapt(c,factor,baseheight,basedepth)
                                nv = nv + 1
                                nc = c.next
                                if not nc then
                                    local hv = c.parts
                                    if hv then
                                        for i=1,#hv do
                                            local c = characters[hv[i].glyph]
                                            if c then
                                                adapt(c,factor,baseheight,basedepth)
                                                ns = ns + 1
                                            end
                                        end
                                    end
                                    break
                                end
                            else
                                break
                            end
                        end
                        if trace_tweaking then
                            report_tweak("adapting extensible (%i sizes, %i parts) %U",target,original,k,nv,ns)
                        end
                    end
                end
            end
        end
    end

    function mathtweaks.accentdimensions(target,original,parameters)
        local list = parameters.list or { "over", "under" }
        if list then
            local characters = target.characters
            local baseheight = target.mathparameters.AccentBaseHeight or 0
            local basedepth  = target.mathparameters.AccentBaseDepth  or 0
            for k, v in sortedhash(list) do -- sort for tracing
                local t = type(v)
                if t == "string" then
                    v = candidates[v]
                    t = type(v)
                end
                if t == "table" then
                    process(target,original,characters,v,baseheight,basedepth)
                end
            end
        end
    end

end

do

    local addprivate     = fonts.helpers.addprivate
    local privateslot    = fonts.helpers.privateslot
    local newprivateslot = fonts.helpers.newprivateslot

    -- no checking for present extensibles

    function mathtweaks.addrules(target,original,parameters)
        local characters = target.characters
        local thickness  = target.mathparameters.OverbarRuleThickness
        local width      = target.parameters.quad / 3
        local step       = width / 2
        local quarter    = thickness / 4
        local half       = thickness / 2
        local double     = thickness * 2
        local done       = false
        characters[0x203E] = { -- middle used for all kind
            width    = width,
            height   = half,
            depth    = half,
            yoffset  = - half,
            unicode  = 0x203E,
            commands = { { "rule", thickness, width } },
            parts    = {
                { advance = width, ["end"] = step, glyph = 0x203E, start = 0 },
                { advance = width, ["end"] = 0,    glyph = 0x203E, start = step, extender = 1 },
            },
            partsorientation = "horizontal",
        }
        local function build(target,leftarrow,rightarrow)
            if leftarrow and rightarrow then
                -- actually the same sort of code as we have for antykwa
                local left  = leftarrow.parts
                local right = rightarrow.parts
                if left and right then
                    local leftline   = right[1].glyph
                    local rightline  = left[#left].glyph
                    local leftdata   = characters[leftline]
                    local rightdata  = characters[rightline]
                    local leftwidth  = leftdata.width
                    local rightwidth = rightdata.width
                    local result     = characters[target] -- copytable(leftdata)
                    if not result or result.width == 0 then
                        result = {
                            height   = leftdata.height,
                            depth    = leftdata.depth,
                            width    = 0.9*leftwidth + rightwidth,
                            unicode  = target,
                            commands = {
                                slotcommand[0][leftline],
                                leftcommand[0.1*leftwidth],
                                slotcommand[0][rightline],
                            },
                        }
                        characters[target] = result
                    end
                    result.parts = {
                        { advance = leftwidth, glyph = leftline, ["end"] = .9*leftwidth, start = 0 },
                        { advance = rightwidth, glyph = rightline, ["end"] = .1*leftwidth, start = .9*rightwidth, extender = 1 },
                    }
                    result.partsorientation = "horizontal"
                    done = registerdone(done,target)
                end
            end
        end
        build(0x305,characters[0x20D6],characters[0x20D7]) -- overbar  accent arrows
        build(0x332,characters[0x20EE],characters[0x20EF]) -- underbar accent arrows
        --
        -- lucida lacks them ...
        --
        if not characters[0x23B4] then
            local depth  = 0
            local height = 5 * thickness
            local tpiece = addprivate(target,"bracket-piece-top",{
                width    = thickness,
                height   = height,
                depth    = depth,
                commands = { upcommand[thickness], { "rule", 4 * thickness, thickness } },
            })
            local mpiece = addprivate(target,"bracket-piece-top-middle",{
                width    = width,
                height   = height,
                depth    = depth,
                commands = { upcommand[4*thickness], { "rule", thickness, width } },
            })
            characters[0x23B4] = { -- over
                width      = double + width,
                height     = height,
                depth      = depth,
                unicode    = 0x23B4,
                extensible = false, -- needs checking
                commands = {
                    slotcommand[0][tpiece],
                    slotcommand[0][mpiece],
                    slotcommand[0][tpiece],
                },
                parts    = {
                    { advance = thickness, glyph = tpiece, ["end"] = 0, start = half },
                    { advance = width,     glyph = mpiece, ["end"] = step, start = step, extender = 1 },
                    { advance = thickness, glyph = tpiece, ["end"] = half, start = 0 },
                },
                partsorientation = "horizontal",
            }
            done = registerdone(done,0x23B4)
        end
        if not characters[0x23B5] then
            local depth  = 0
            local height = 5 * thickness
            local bpiece = addprivate(target,"bracket-piece-bottom",{
                width    = thickness,
                height   = height,
                depth    = depth,
                yoffset  = depth,
                commands = { { "rule", 4 * thickness, thickness } },
            })
            local mpiece = addprivate(target,"bracket-piece-bottom-middle",{
                width    = width,
                height   = height,
                depth    = depth,
                commands = { { "rule", thickness, width } },
            })
            characters[0x23B5] = { -- under
                width      = double + width,
                height     = height,
                depth      = depth,
                unicode    = 0x23B5,
                extensible = false, -- needs checking
                commands = {
                    slotcommand[0][bpiece],
                    slotcommand[0][mpiece],
                    slotcommand[0][bpiece],
                },
                parts    = {
                    { advance = thickness, glyph = bpiece, ["end"] = 0, start = half },
                    { advance = width,     glyph = mpiece, ["end"] = step, start = step, extender = 1 },
                    { advance = thickness, glyph = bpiece, ["end"] = half, start = 0 },
                },
                partsorientation = "horizontal",
            }
            done = registerdone(done,0x23B5)
        end
        --
        feedback_tweak("rules",target,original,done)
    end

    -- vfmath.builders.extension(target)

    local rbe = newprivateslot("radical bar extender")
    local fbe = newprivateslot("fraction bar extender")

    local frp = {
        newprivateslot("flat rule left piece"),
        newprivateslot("flat rule middle piece"),
        newprivateslot("flat rule right piece"),
    }

    local rrp = {
        newprivateslot("radical rule left piece"),
        newprivateslot("radical rule middle piece"),
        newprivateslot("radical rule right piece"),
    }

    local mrp = {
        newprivateslot("minus rule left piece"),
        newprivateslot("minus rule middle piece"),
        newprivateslot("minus rule right piece"),
    }

    local forceextensible_tag <const> = tex.charactertagcodes.forceextensible

    local function useminus(target,unicode,characters,parameters,skipfirst,what,tounicode)
        local minus   = characters[0x2212]
        local parts   = minus.parts
        if parameters == true then
            parameters = { }
        end
        if parts then
            minus.tag = forceextensible_tag
            what  = copytable(what)
            parts = copytable(parts)
            local xscale   = parameters.xscale  or 1 -- why not applied to width ?
            local yscale   = parameters.yscale  or 1
            local mwidth   = minus.width
            local mheight  = minus.height
            local width    = 0
            local height   = (parameters.height  or 1) * mheight
            local xshift   = 0
            local yshift   = (parameters.yoffset or 0) * mheight
            local loverlap = parameters.leftoverlap  or 0
            local roverlap = parameters.rightoverlap or 0
            local loffset  = parameters.leftoffset   or 0
            local roffset  = parameters.rightoffset  or 0
            if skipfirst then
                remove(parts,1)
                remove(what,1)
            end
            height = height / 2
            yshift = yshift + height
            for i=1,#parts do
                local part   = parts[i]
                local glyph  = part.glyph
                local gdata  = characters[glyph]
                local width  = gdata.width
                local xshift = 0
                if i == 1 and loverlap ~= 0 then
                    xshift = loverlap * width
                    width  = width - xshift
                elseif i == #parts and roverlap ~= 0 then
                    width  = width - roverlap * width
                end
                characters[what[i]] = {
                    height   = height,
                    depth    = height,
                    width    = width,
                    advance  = gdata.width,
                    unicode  = 0xFFFD, -- we have no unicode ignore ... maybe zws
                    commands = {
                        leftcommand[xshift],
                        downcommand[yshift],
                        { "slot", 0, glyph, xscale, yscale },
                    },
                }
                -- we should overlap more ...
                if part["start"] >= width then
                    part["start"] = width
                end
                if part["end"] >= width then
                    part["end"] = width
                end
                part.advance = width
                part.glyph   = what[i]
            end
            characters[what[1]].unicode  = unicode -- nice for tagging
            xshift = loffset * mwidth + loverlap * mwidth
            width  = mwidth - xshift - roffset * mwidth - roverlap * mwidth
            characters[unicode] = {
                -- base character
                height   = height,
                depth    = height,
                width    = width,
                tag      = forceextensible_tag,
                -- not needed as we force:
                commands = {
                    leftcommand[xshift],
                    downcommand[yshift],
                    { "slot", 0, 0x2212, xscale, yscale },
                },
                unicode          = tounicode or unicode,
                -- extensibles
                parts            = parts,
                partsorientation = "horizontal",
            }
        end
    end

    -- add minus parts of not there and create clipped clone

    local function checkminus(target,unicode,characters,parameters,skipfirst,what,tounicode)
        local minus = characters[unicode]
        local parts = minus.parts
        if parameters == true then
            parameters = { }
        end
        local p_normal = 0
        local p_flat   = 0
        local mwidth   = minus.width
        local height   = minus.height
        local depth    = minus.depth
        local loffset  = parameters.leftoffset  or 0
        local roffset  = parameters.rightoffset or 0
        local lshift   = mwidth * loffset
        local rshift   = mwidth * roffset
        local width    = mwidth - lshift - rshift
        if parts then
         -- print("minus has parts")
            if lshift ~= 0 or width ~= mwidth then
                parts = copytable(parts)
                for i=1,#parts do
                    local part    = parts[i]
                    local glyph   = part.glyph
                    local gdata   = characters[glyph]
                    local width   = gdata.width
                    local advance = part.advance
                    local lshift = 0
                    if i == 1 and left ~= 0 then
                        lshift  = loffset * width
                        width   = width - lshift
                        advance = advance - lshift
                    elseif i == #parts and roffset ~= 0 then
                        width   = width - rshift
                        advance = advance - rshift
                    end
                    characters[what[i]] = {
                        height   = height,
                        depth    = depth,
                        width    = width,
                        commands = {
                         -- { "offset", lshift, 0, glyph },
                            leftcommand[lshift],
                            slotcommand[0][glyph],
                        },
                    }
                    part.glyph   = what[i]
                    part.advance = advance
                end
                minus.parts = parts
                minus.partsorientation = "horizontal"

            end
        else
            local f_normal = formatters["M-NORMAL-%H"](unicode)
         -- local p_normal = hasprivate(main,f_normal)
            p_normal = addprivate(target,f_normal,{
                height   = height,
                width    = width,
                commands = {
                 -- { "offset", lshift, 0, unicode },
                    push,
                    leftcommand[lshift],
                    slotcommand[0][unicode],
                    pop,
                },
            })
         -- local step = width/2
            local step = .8*width
            minus.parts = {
                { extender = 0, glyph = p_normal, ["end"] = step, start = 0,    advance = width },
                { extender = 1, glyph = p_normal, ["end"] = step, start = step, advance = width },
                { extender = 0, glyph = p_normal, ["end"] = 0,    start = step, advance = width },
            }
            minus.partsorientation = "horizontal"
        end
        minus.unicode = tounicode or unicode
    end

    function mathtweaks.replacerules(target,original,parameters)
        local characters = target.characters
        local minus      = parameters.minus
        local fraction   = parameters.fraction
        local radical    = parameters.radical
        local stacker    = parameters.stacker
        if minus then
            checkminus(target,0x2212,characters,minus,false,mrp)
        end
        if fraction then
            useminus(target,fbe,characters,fraction,false,frp,0x2044) -- division slash
        end
        if radical and not characters[rbe] then
            local skipfirst = true
            if radical.skipfirst == false then -- explicit
                skipfirst = false
            end
            useminus(target,rbe,characters,radical,skipfirst,rrp,0x2061)   -- apply function
        end
        if stacker then
            useminus(target,0x203E,characters,stacker,false,frp)
        end
    end

    local force = false  experiments.register("math.arrows", function(v) force = v end)

    local function tighten(target,unicode,left,right,squeeze,yoffset)
        local name = formatters["math tightened %U %.3N %.3N %.3N %.3N"](unicode,left,right,squeeze,yoffset)
        local slot = privateslot(target,name)
        if not slot then
            local characters = target.characters
            local data   = copytable(characters[unicode])
            local width  = data.width
            data.advance = width
            data.width   = width * (1-left-right)
            data.xoffset = width * -left
            if squeeze ~= 1 then
                data.effect  = { squeeze = squeeze }
            end
            if yoffset ~= 0 then
                data.yoffset = (data.height or 0) * yoffset
            end
            slot = addprivate(target,name,data)
        end
        return slot
    end

    local function create(target,unicode,list,overloads)
        local characters = target.characters
        local chardata   = characters[unicode]
        if chardata then
            local endpoint = unicode
            while chardata.next do
                chardata = characters[chardata.next]
            end
            if chardata and (force or overloads[unicode] == false or not chardata.parts) then
                if not list then
                 -- chardata.parts = nil -- when we test
                else
                    local overload = overloads[unicode]
                    local parts    = { }
                    for i=1,#list do
                        local part    = list[i]
                        local glyph   = part.glyph or unicode
                        local check   = overloads[glyph]
                        local left    = (check and check.left   ) or part.left    or 0
                        local right   = (check and check.right  ) or part.right   or 0
                        local squeeze =  check and check.squeeze or 1
                        local yoffset =  check and check.yoffset or 0
                        if left~= 0 or right ~= 0 or squeeze ~= 1 or yoffset ~= 0 then
                            glyph = tighten(target,glyph,left,right,squeeze,yoffset)
                        end
                        local width = characters[glyph].width
                        local step  = width/2
                        if part.extensible then
                            parts[#parts+1] = {
                                advance  = width,
                                glyph    = glyph,
                                ["end"]  = step,
                                start    = step,
                                extender = 1,
                            }
                        else
                            parts[#parts+1] = {
                                advance = width,
                                glyph   = glyph,
                                ["end"] = 0,
                                start   = step,
                            }
                        end
                    end
                    if #parts == #list then
                        chardata.parts            = parts
                        chardata.partsorientation = "horizontal"
                    end
                end
            end
        end
    end

    -- Unicode math lacks the arrow snippet while it does have fence snippets. Also, some
    -- fonts have a relbar that doesn't match the double arrow.
    --
    -- {
    --     tweak = "addarrows",
    --     list  = { [0x3D] = { squeeze = .85, yoffset = .0975 } }
    -- },
    --
    -- We have no begin and end snippet, so I played with centering and rules at the edges
    --
    -- [0x21A9] = { -- hookleftarrow
    --     { glyph = 0x2212, left = slack, extensible = true },
    --     { glyph = 0x21A9, right = slack },
    --     { glyph = 0x2212, right = slack, extensible = true },
    -- }
    --
    -- but in the end rejected it.

    local function initialize(left, right, slack)
        -- We save some space with locals. When no glyph is given the unicode itself is
        -- used which also saves some.
        local single = { glyph = 0x2212, left = slack, right = slack, extensible = true }
        local double = { glyph = 0x003D, left = slack, right = slack, extensible = true }
        local triple = { glyph = 0x2261, left = slack, right = slack, extensible = true }
        ----- spacer = { glyph = 0x0020, left = slack, right = slack, extensible = true }
        local slackslack  = { left = slack, right = slack }
        local leftslack   = { left = left,  right = slack }
        local slackright  = { left = slack, right = right }
        ----- centered    = { spacer, { }, spacer }
        local centered    = false -- the luametatex engine does this
        local singleright = { single, slackright }
        local leftsingle  = { leftslack,  single }
        return {
            --
            [0x002D] = { { left = slack, right = slack, glyph = 0x2212 }, single }, -- rel
         -- [0x2212] = { { left = slack, right = slack, glyph = 0x2212 }, single }, -- rel
            --
            [0x2190] = leftsingle, -- leftarrow
            [0x219E] = leftsingle, -- twoheadleftarrow
            [0x21BC] = leftsingle, -- leftharpoonup
            [0x21BD] = leftsingle, -- leftharpoondown
            --
            [0x2192] = singleright, -- rightarrow
            [0x21A0] = singleright, -- twoheadrightarrow
            [0x21C0] = singleright, -- rightharpoonup
            [0x21C1] = singleright, -- rightharpoondown
            --
            [0x003D] = { slackslack, double }, -- equaltext
            [0x2261] = { slackslack, triple }, -- triplerel
            [0x27F8] = { leftslack,  double }, -- Leftarrow
            [0x27F9] = { double, slackright }, -- Rightarrow
            --
            [0x21A9] = centered, -- hookleftarrow
            [0x21AA] = centered, -- hookrightarrow
            [0x21CB] = centered, -- leftrightharpoons
            [0x21CC] = centered, -- rightleftharpoons
            [0x21C4] = centered, -- rightoverleftarrow
            [0x21C6] = centered, -- leftoverrightarrow
            [0x21A6] = centered, -- mapsto
            --
            [0x203E] = { slackslack, { left = slack, right = slack, extensible = true } }, -- bar
            --
            [0x27F7] = { { glyph = 0x2190, left = left, right = slack }, single, { glyph = 0x2192, left = slack, right = right } }, -- leftrightarrow rightleftarrow
            [0x27FA] = { { glyph = 0x27F8, left = left, right = slack }, double, { glyph = 0x27F9, left = slack, right = right } }, -- Leftrightarrow Rightleftarrow
        }
    end

    datasets.addarrows = { }

    function mathtweaks.addarrows(target,original,parameters)
        local overloads = parameters.list  or { } -- { [unicode] = { left = .1, right = .1 } }
        local left      = parameters.left  or 0.05
        local right     = parameters.right or 0.05
        local slack     = parameters.slack or 0.1
        local arrows    = initialize(left,right,slack)
         -- inspect(arrows)
        for unicode, list in sortedhash(arrows) do
            create(target,unicode,list,overloads)
        end
        datasets.addarrows = sortedkeys(arrows)
        if trace_tweaking then
            report_tweak("arrows added",target,original)
        end

    end

end



do -- this could be combined with the previous

    function mathtweaks.addparts(target,original,parameters)
        local characters = target.characters
        local list       = parameters.list
        if list then
            for unicode, data in sortedhash(list) do
                local template = data.template
                if template then
                    local source = characters[template]
                    local target = characters[unicode]
                    if source and target then
                        local sequence = data.sequence
                        if sequence then
                            -- we should follow the next chain first
                            local parts = source.parts
                            if parts then
                                local p = { }
                                for i=1,#sequence do
                                    local step  = sequence[i]
                                    local glyph = step.glyph
                                    if glyph == "first" or glyph == "last" then
                                        local g = glyph == "first" and 1 or #parts
                                        local c = fastcopy(parts[g])
                                        local f = step.factor
                                        if f then
                                            c["end"]  = f * (c["end"] or 0)
                                            c.start   = f * (c.start  or 0)
                                        end
                                        p[#p+1] = c
                                    else
                                        local c = characters[glyph]
                                        if c then
                                            p[#p+1] = {
                                                glyph   = glyph,
                                                advance = c.width,
                                                start   = 0,
                                                ["end"] = 0,
                                            }
                                        end
                                    end
                                end
                                if #p > 0 then
                                    target.parts = p
                                    if not data.horizontal then
                                        target.partsorientation = "vertical"
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end
    end

end

function mathtweaks.action(target,original,parameters)
    local action = parameters.action
    if type(action) == "function" then
        action(target,original,parameters)
    end
end

do

    local list = {
        { 0x00A0, "s", 1         }, -- nbsp
        { 0x2000, "q", 1/2       }, -- enquad
        { 0x2001, "q", 1         }, -- emquad
        { 0x2002, "q", 1/2       }, -- enspace
        { 0x2003, "q", 1         }, -- emspace
        { 0x2004, "q", 1/3       }, -- threeperemspace
        { 0x2005, "q", 1/4       }, -- fourperemspace
        { 0x2006, "q", 1/6       }, -- sixperemspace
        { 0x2007, "c", byte('0') }, -- figurespace
        { 0x2008, "c", byte('.') }, -- punctuationspace
        { 0x2009, "q", 1/8       }, -- breakablethinspace
        { 0x200A, "q", 1/8       }, -- hairspace
        { 0x200B, "q", 0         }, -- zerowidthspace
        { 0x202F, "q", 1/8       }, -- narrownobreakspace
        { 0x205F, "s", 1/2       }, -- math thinspace
    }

    datasets.checkspacing = list

    function mathtweaks.checkspacing(target,original,parameters)
        local characters = target.characters
        local parameters = target.parameters
        for i=1,#list do
            local entry   = list[i]
            local unicode = entry[1]
            local data    = characters[unicode]
            if not data then
                local method   = entry[2]
                local fraction = entry[3]
                local width    = 0
                local height   = 0
             -- local depth    = 0
                if method == "c" then
                    local template = characters[fraction]
                    width  = template.width
                    height = template.height
                 -- depth  = template.depth
                elseif method == "s" then
                    width  = fraction * parameters.space -- space
                    height = 0
                 -- depth  = 0
                else
                    width = fraction * parameters.quad  -- quad
                    height = 0
                 -- depth  = 0
                end
                if trace_tweaking then
                    report_tweak("setting width of %U to %p",target,original,unicode,width)
                end
                characters[unicode] = {
                    width    = width,
                 -- advance  = width,
                    height   = height,
                 -- depth    = depth,
                    unicode  = unicode,
                    commands = {
                     -- { "slot", 0, 32 },
                    },
                }
            end
        end
    end

end

do

    -- mirror
    -- smaller

    local nps = fonts.helpers.newprivateslot

    local radicalbarextender = nps("radical bar extender") -- we reserve it here

    local list = {
        0x221A,
    }

    local function fix(target,original,characters,unicode)
        local data = characters[unicode]
        if data then
            local height = data.height or 0
            local depth  = data.depth or 0
            if depth > height then
                if trace_tweaking then
                    report_tweak("swapping height and depth of radical %U",target,original,unicode)
                end
                if data.rorrim then
                    -- the original does the magic
                else
                    data.height  = (data.height or 0) + (data.depth or 0)
                    data.yoffset = data.depth
                    data.depth   = 0
                end
            end
            local smaller = data.smaller
            if smaller then
                fix(target,original,characters,smaller)
            end
         -- local mirror = data.mirror
         -- if mirror then
         --     fix(target,original,characters,mirror)
         -- end
            local next = data.next
            if next then
                fix(target,original,characters,next)
            else
             -- -- assume no depth
             -- local parts = data.parts
             -- if parts then
             --     fix(target,original,characters,parts[1].glyph)
             -- end
            end
        end
    end

    function mathtweaks.fixradicals(target,original,parameters)
        local characters = target.characters
        for i=1,#list do
            local unicode = list[i]
            fix(target,original,characters,unicode)
        end
    end

    local function fix(target,original,characters,u,l)
        local data = characters[u]
        if data then
         -- data.innerlocation = l.location == "right" and 2 or 1
            data.innerlocation = l.location == "right" and "right" or "left"
            data.innerxoffset  = (l.hfactor or 1) *  (data.width  or 0)
            data.inneryoffset  = (l.vfactor or 1) * ((data.height or 0) + (data.depth or 0))
        end
    end

    function mathtweaks.radicaldegreeanchors(target,original,parameters)
        local list = parameters.list
        if list then
            local characters = target.characters
            for unicode, l in sortedhash(list) do -- resolve variants
                local u = detail(characters,unicode) or unicode
                if type(u) == "table" then
                    for i=1,#u do
                        fix(target,original,characters,u[i],l)
                    end
                else
                    fix(target,original,characters,u,l)
                end
            end
        end
    end

    local function fix(target,original,characters,u,l)
        local data = characters[u]
        if data then
            data.leftmargin  = (l.left  or 1) * (data.width  or 0)
            data.rightmargin = (l.right or 1) * (data.width  or 0)
        end
    end

    function mathtweaks.radicalbodymargins(target,original,parameters)
        local list = parameters.list
        if list then
            local characters = target.characters
            for unicode, l in sortedhash(list) do -- resolve variants
                local u = detail(characters,unicode) or unicode
                if type(u) == "table" then
                    for i=1,#u do
                        fix(target,original,characters,u[i],l)
                    end
                else
                    fix(target,original,characters,u,l)
                end
            end
        end
    end

end

do

    local done = nil

    local function fix(target,original,characters,unicode,axis)
        if done[unicode] then
            return
        end
        done[unicode] = true
        local data = characters[unicode]
        if data then
            local height = data.height or 0
            local depth  = data.depth or 0
            if trace_tweaking then
                report_tweak("swapping height and depth of %U",target,original,unicode)
            end
            local half = (height + depth)/2
            if data.rorrim then
                -- the original does the magic
            else
                data.yoffset = depth - (half - axis)
            end
            height = half + axis
            depth  = half - axis
            data.height  = height
            data.depth   = depth
            local smaller = data.smaller
            if smaller then
                fix(target,original,characters,smaller,axis)
            end
            local mirror = data.mirror
            if mirror then
                fix(target,original,characters,mirror,axis)
            end
            local next = data.next
            if next then
                fix(target,original,characters,next,axis)
            end
        end
    end

    function mathtweaks.fixoldschool(target,original,parameters)
        local characters = target.characters
        local list       = mathtweaks.subsets.integrals
        local also       = getalso(target,original)
        local axis       = target.mathparameters.AxisHeight
        done = { }
        for i=1,#list do
            local unicode = list[i]
            fix(target,original,characters,unicode,axis)
        end
        if also then
            local a = also[u]
            if a then
                for i=1,#a do
                    fix(target,original,characters,a[i],axis)
                end
            end
        end
        done = nil
    end

    -- After the next one I rewarded myself by (again) watching Joe Parrish interpretation
    -- of Shostakovich 10 Mvmt. II - Metal several times (video on yt, track on bandcamp)
    -- ... timestamp: awaiting the new Albion (Official) single; their work comes in parts.

    function mathtweaks.fixintegrals(target,original,parameters)
        local characters = target.characters
        local integral   = characters[0x222B]
        while integral do
            local next = integral.next
            if next then
                integral = characters[next]
            else
                break
            end
        end
        if integral and not integral.parts then
            local top = characters[0x2320]
            local mid = characters[0x23AE]
            local bot = characters[0x2321]
            if top and mid and bot then
                top = top.height
                mid = mid.height
                bot = bot.height
                integral.partsitalic = integral.italic
                integral.parts       = {
                    { advance = bot, ["end"] = bot/3, glyph = 0x2321, start = bot/3  },
                    { advance = mid, ["end"] = mid/2, glyph = 0x23AE, start = mid/2, extender = 1 },
                    { advance = top, ["end"] = top/3, glyph = 0x2320, start = top/3  },
                }
                integral.partsorientation = "vertical"
                if trace_tweaking then
                    report_tweak("fixing the integral extensible",target,original)
                end
            end
        else
            report_tweak("no need to fix the integral extensible",target,original)
        end
    end

end

do

    local list = { 0x2061, 0x2062, 0x2063, 0x2064 }

    datasets.wipecues = list

    function mathtweaks.wipecues(target,original,parameters)
        local characters = target.characters
        local tobewiped  = parameters.list or list
        local done       = false
        for i=1,#tobewiped do
            local unicode = tobewiped[i]
            characters[unicode] = {
                width   = 0,
                height  = 0,
                depth   = 0,
                unicode = unicode,
                commands = {
                    { "rule" , 0, 0 }
                }
            }
            done = registerdone(done,unicode)
        end
        feedback_tweak("wipecues",target,original,done)
    end

end

do

    local mapping = {
        [0x002F] = 0x2044,
    }

    datasets.fixslashes = mapping

    function mathtweaks.fixslashes(target,original,parameters)
        local characters = target.characters
     -- local done       = false
        for normal, weird in sortedhash(mapping) do
            local normalone  = characters[normal]
            local weirdone   = characters[weird]
            if normalone and weirdone and not normalone.next then
                normalone.next = weirdone.next
             -- done = registerdone(done,normal)
            end
            weirdone = copytable(normalone)
            characters[weird] = weirdone
            weirdone.unicode = weird
        end
     -- feedback_tweak("fixslashes",target,original,done)
        if trace_tweaking then
            report_tweak("slashes fixed",target,original)
        end
     end

end

do -- see pagella for an extensive example

    local nps = fonts.helpers.newprivateslot

    local mapping = {
        [0x0300] = { 0x0060, false },
        [0x0308] = { 0x00A8, false },
        [0x0304] = { 0x00AF, false }, -- 305
        [0x0301] = { 0x00B4, false },
        [0x0302] = { 0x02C6, true  },
        [0x030C] = { 0x02C7, true  },
        [0x0306] = { 0x02D8, false },
        [0x0307] = { 0x02D9, false },
        [0x030A] = { 0x02DA, false },
        [0x0303] = { 0x02DC, true  },
        [0x20DB] = { 0x20DB, false },
-- [0x20EF] = { 0x20EF, false },
    }

    datasets.fixaccents     = mapping
    datasets.extendaccents  = mapping
    datasets.flattenaccents = mapping
    datasets.copyaccents    = mapping

    -- local flat = stretchingdata.flataccent
    -- if flat then
    --     -- Nasty! xoffset needed. Check this when we patch vf.
    --     local flatdata = characters[flat]
    --     flatdata.width     = width
    --     flatdata.advance   = 0
    --     flatdata.topanchor = topanchor
    --     flatdata.xoffset   = width + topanchor
    -- end

local cdata = characters.data

    function mathtweaks.fixaccents(target,original,parameters)
        local characters = target.characters
        local done       = false
        for stretching, entry in sortedhash(mapping) do
            local alias  = entry[1]
            local stretchingdata = characters[stretching]
            if stretchingdata and stretchingdata.width == 0 then
             -- if false then
             --     local b = target.descriptions[stretching].boundingbox
             --     if b then
             --         local llx = b[1] * target.parameters.hfactor
             --         local urx = b[3] * target.parameters.hfactor
             --         local width = urx - llx
             --         stretchingdata.width        = width
             --         stretchingdata.xoffset      = - llx
             --         stretchingdata.advance      = urx
             --         if not stretchingdata.anchored then -- safeguard
             --             stretchingdata.topanchor    = width/2
             --             stretchingdata.bottomanchor = width/2
             --         end
             --     end
             -- else
                    local topanchor = stretchingdata.topanchor or 0
                    local width     = -topanchor
                    topanchor = width/2
                    stretchingdata.width     = width
                    stretchingdata.advance   = 0
                    if not stretchingdata.anchored then -- safeguard
                        stretchingdata.topanchor = topanchor
                    end
                    stretchingdata.commands  = { rightcommand[width + topanchor], charcommand[stretching] }
             -- end
                done = registerdone(done,stretching)
            end
        end
        feedback_tweak("fixaccents",target,original,done)
    end

    function mathtweaks.checkaccents(target,original,parameters)
        local characters = target.characters
        local done       = false
        local factor     = target.parameters.hfactor
        local ignore     = parameters.ignore
        for unicode, data in sortedhash(characters) do
            if ignore and ignore[unicode] then
                -- well, see stix
            else
                local width = data.width
                if width == 0 then
                    local d = chardata[data.unicode or unicode]
                    if d then
                        local c = d and d.category
                        if c == "mn" or c == "sk" or c == "lm" then -- can probably can go
                            local b = target.descriptions[unicode]
                            if b then
                                b = b.boundingbox
                            end
                            if b and not d.anchored then
                                local topanchor = data.topanchor or 0
                                local llx       = b[1] * factor
                                local urx       = b[3] * factor
                                data.advance = data.width
                             -- report_math("%U : %C : %p",unicode,data.unicode or unicode,topanchor)
                                if topanchor ~= 0 then -- true then
                                    -- stix has 0x20D6 and 0x20D7 with top accents but the ssty
                                    -- 0x20D7 (1 and 2) without and no ssty 0x20D6 at all. We just
                                    -- intercept it and accept that limitation.
                                 -- width         = - topanchor
                                    width = 2 * (topanchor - llx)
                                 -- data.commands = {
                                 --  -- rightcommand[width+width/2],
                                 --     rightcommand[-llx],
                                 --     slotcommand[0][unicode]
                                 -- }
                                    data.xoffset = -llx
                                else
                                    width         = urx - llx
                                    data.commands = {
                                        leftcommand[llx],
                                        slotcommand[0][unicode]
                                    }
                                end
                                data.width        = width
                                data.topanchor    = width/2
                                data.bottomanchor = width/2
                            end
                            done = registerdone(done,unicode)
                        end
                    end
                end
            end
        end
        feedback_tweak("checkaccents",target,original,done)
    end

    -- all  true|number  false

    function mathtweaks.extendaccents(target,original,parameters)
        local characters = target.characters
        local all        = parameters.all
        local count      = tonumber(all)
        local done       = false
        for stretching, entry in sortedhash(mapping) do
            local extend = entry[2]
            if extend then
                local last = characters[stretching]
                local cnt  = 1
                local okay = false
                while last do
                    if all or (count and cnt > count) then
                        last.extensible = true
                        local flataccent = last.flataccent
                        if flataccent then
                            characters[flataccent].extensible = true
                            okay = true
                        end
                    end
                    local n = last.next
                    if n then
                        last = characters[n]
                    else
                        last.extensible = true
                        local flataccent = last.flataccent
                        if flataccent then
                            characters[flataccent].extensible = true
                            okay = true
                        end
                        break
                    end
                    cnt = cnt + 1
                end
                if okay then
                    done = registerdone(done,stretching)
                end
            end
        end
        feedback_tweak("extendaccents",target,original,done)
    end

    -- force     true     false
    -- height    factor   0.8
    -- offset    factor   0.9|calculated
    -- squeeze   factor   0.1|calculated

    local f_flat = formatters["flat accent %05X"]

    function mathtweaks.flattenaccents(target,original,parameters)
        local characters = target.characters
        local force   = parameters.force
        local squeeze = parameters.squeeze or 0.85
        local ofactor = parameters.offset  or (squeeze/8.5)
        local hfactor = parameters.height  or 0.95 -- (1 - ofactor)
        local done    = false
        for stretching, entry in sortedhash(mapping) do
            local code  = stretching
            local last  = characters[stretching]
            while last do
                if force or not last.flataccent then
                    local slot   = nps(f_flat(code))
                    local height = last.height or 0
                    characters[slot] = {
                        width        = last.width,
                        depth        = last.depth,
                        height       = last.height * hfactor,
                        topanchor    = last.topanchor,
                        bottomanchor = last.bottomanchor,
                        commands     = { { "offset", 0, ofactor * height, code, 1, squeeze } },
                     -- commands     = { slotcommand[0][code] },
                     -- effect       = { squeeze = squeeze },
                     -- next         = last.next,
                        unicode      = last.unicode,
                    }

                    last.flataccent  = slot
                    done = registerdone(done,stretching)
                end
                code = last.next
                if code then
                    last = characters[code]
                else
                    break
                end
            end
        end
        feedback_tweak("flattenaccents",target,original,done)
    end

    function mathtweaks.copyaccents(target,original,parameters)
        local characters = target.characters
        local done       = false
        for stretching, entry in sortedhash(mapping) do
            local alias = entry[1]
            if alias ~= stretching then
                local stretchingdata = characters[stretching]
                if stretchingdata then
                    -- we need to nil [x|y]offsets
                    characters[alias] = {
                        width     = stretchingdata.width,
                        height    = stretchingdata.height,
                        depth     = stretchingdata.depth,
                        next      = stretchingdata.next,
                     -- commands  = { charcommand[stretching] },
                        commands  = stretchingdata.commands or { charcommand[stretching] },
                        topanchor = stretchingdata.topanchor,
                     -- unicode   = stretching,  -- when we alias to combiners
                        unicode   = alias, -- when we keep the original
                    }
                    done = registerdone(done,stretching)
                end
            end
        end
        feedback_tweak("copyaccents",target,original,done)
    end

    function mathtweaks.keepbases(target,original,parameters)
        local characters = target.characters
        local done       = false
        local list       = parameters.list
        if list == "default" then
            list = sortedkeys(mapping)
        end
        if list and #list > 0 then
            for i=1,#list do -- assumes sorting
                local unicode  = list[i]
                local chardata = characters[unicode]
                if chardata then
                    chardata.keepbase = true
                    done = registerdone(done,unicode)
                end
            end
        else
            -- maybe also hash
        end
        feedback_tweak("keepbases",target,original,done)
    end

end

do

    function mathtweaks.inspect(target,original,parameters)
        local characters = target.characters
        local slot = parameters.slot or parameters.unicode
        if slot then
            -- todo: show unicode
            report_math(formatters["%C data:"](slot))
            inspect(characters[slot])
        end
    end

end

-- do
--
--     local single <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,
--             commands = { char, left, char },
--         }
--         characters[triple] = {
--             unicode  = triple,
--             width    = 3*width - 2*advance,
--             height   = height,
--             depth    = depth,
--             commands = { char, left, char, left, char },
--         }
--         if trace_tweaking then
--             report_tweak("double %U and triple %U equals added",target,original,double,triple)
--         end
--     end
--
-- end

do

    local function jointwo(characters,force,unicode,ds,u1,d12,u2)
        if force or not characters[unicode] then
            local c1 = characters[u1]
            local c2 = characters[u2]
            if c1 and c2 then
                local w1 = c1.width
                local w2 = c2.width
                local width
                if d12 == false then
                    d12   = 0
                    width = w2
                elseif d12 < 0 then
                    d12   = d12 * w2
                    width = w2
                else
                    d12   = d12 * ds
                    width = w1 + w2 - d12
                end
                characters[unicode] = {
                    unicode     = unicode,
                    width       = width,
                    height      = max(c1.height or 0, c2.height or 0),
                    depth       = max(c1.depth  or 0, c2.depth  or 0),
                    keepvirtual = true, -- needs testing
                    commands    = {
                     -- { "inspect" },
                     -- { "trace" },
                        slotcommand[0][u1],
                     -- { "trace" },
                        d12 ~= 0 and leftcommand[d12] or false,
                        slotcommand[0][u2],
                     -- { "trace" },
                    },
                }
            end
        end
    end

    local function jointhree(characters,force,unicode,ds,u1,d12,u2,d23,u3)
        if force or not characters[unicode] then
            local c1 = characters[u1]
            local c2 = characters[u2]
            local c3 = characters[u3]
            if c1 and c2 and c3 then
                local w1 = c1.width
                local w2 = c2.width
                local w3 = c3.width
                d12 = d12 * ds
                d23 = d23 * ds
                characters[unicode] = {
                    unicode  = unicode,
                    width    = w1 + w2 + w3 - d12 - d23,
                    height   = max(c1.height or 0, c2.height or 0, c3.height or 0),
                    depth    = max(c1.depth  or 0, c2.depth  or 0, c3.depth  or 0),
                    commands = {
                        slotcommand[0][u1],
                        d12 ~= 0 and leftcommand[d12] or false,
                        slotcommand[0][u2],
                        d23 ~= 0 and leftcommand[d23] or false,
                        slotcommand[0][u3],
                    }
                }
            end
        end
    end

    function mathtweaks.addequals(target,original,parameters)
        local characters = target.characters
        local step       = target.parameters.size/18
        local force      = parameters.force
force = true
        jointwo  (characters,force,0x2254,step,0x03A,0,0x03D)         -- :=
        jointhree(characters,force,0x2A74,step,0x03A,0,0x03A,0,0x03D) -- ::=
        jointwo  (characters,force,0x2A75,step,0x03D,0,0x03D)         -- ==
        jointhree(characters,force,0x2A76,step,0x03D,0,0x03D,0,0x03D) -- ===
    end

end

do

    -- If we really want, we can have variants that also match radicals but in practice
    -- radicals and actuarians are never seen together. We could also have a smaller
    -- extender.

    local nps = fonts.helpers.newprivateslot

    local radical             <const> = 0x0221A
    local actuarianrightlong  <const> = nps("delimited right annuity long") -- 0x020E7
    local actuarianrightshort <const> = nps("delimited right annuity short")
    local actuarianleftlong   <const> = nps("delimited left annuity long" )
    local actuarianleftshort  <const> = nps("delimited left annuity short" )
    local placehold           <const> = nps("delimited ghost annuity")

    local actuarianbottomrightlong  <const> = nps("delimited bottom right annuity long") -- 0x020E7
    local actuarianbottomrightshort <const> = nps("delimited bottom right annuity short")
    local actuarianbottomleftlong   <const> = nps("delimited bottom left annuity long" )
    local actuarianbottomleftshort  <const> = nps("delimited bottom left annuity short" )

    function mathtweaks.addactuarian(target,original,parameters)
        local characters = target.characters
        local parameters = target.parameters
        local linewidth  = target.MathConstants.RadicalRuleThickness -- make option
        local basechar   = characters[radical]
        local baseheight = (basechar.height or 0)/2
        local basedepth  = (basechar.depth  or 0)/2
        local basetotal  = baseheight + basedepth
        local used       = baseheight
        --
        characters[placehold] = {
            width    = 2*linewidth,
            height   = baseheight,
            depth    = basedepth,
            unicode  = actuarian, -- whatever
            callback = "devirtualize",
            commands = {
                rightcommand[linewidth],
                downcommand[basedepth],
                { "rule", basetotal, 0 },
            },
        }
        --
        characters[0x020E7] = {
            width    = 6*linewidth,
            height   = baseheight,
            depth    = basedepth,
            unicode  = actuarian,
            callback = "devirtualize",
            commands = {
                upcommand[baseheight-4*linewidth],
                { "rule", linewidth, 4*linewidth },
                downcommand[basetotal/2-linewidth],
                { "rule", basetotal/2, linewidth },
            },
        }
        --
        characters[actuarianrightlong] = {
            width    = 2*linewidth,
            height   = baseheight,
            depth    = basedepth,
            unicode  = actuarian,
            callback = "devirtualize",
            commands = {
                downcommand[basedepth],
                { "rule", basetotal, linewidth },
            },
            parts = {
                {
                    advance  = basetotal,
                    ["end"]  = used,
                    glyph    = actuarianrightlong,
                    start    = 0,
                },
                {
                    advance  = basetotal,
                    ["end"]  = 0,
                    extender = 1,
                    glyph    = actuarianrightlong,
                    start    = used,
                },
            }
        }
        characters[actuarianbottomrightlong] =
            characters[actuarianrightlong]
        characters[actuarianrightshort] = {
            width    = 2*linewidth,
            height   = baseheight,
            depth    = basedepth,
            unicode  = actuarian,
            callback = "devirtualize",
            commands = {
                upcommand[baseheight-4*linewidth],
                { "rule", 4*linewidth, linewidth },
            },
            parts = {
                {
                    advance  = basetotal,
                    ["end"]  = used,
                    glyph    = actuarianrightshort,
                    start    = 0,
                },
                {
                    advance  = basetotal,
                    ["end"]  = 0,
                    extender = 1,
                    glyph    = actuarianrightshort,
                    start    = used,
                },
            }
        }
        characters[actuarianbottomrightshort] = {
            width    = 2*linewidth,
            height   = linewidth,
            depth    = basedepth,
            unicode  = actuarian,
            callback = "devirtualize",
            commands = {
                downcommand[basedepth],
                { "rule", 4*linewidth, linewidth },
            },
            parts = {
                {
                    advance  = basetotal,
                    ["end"]  = used,
                    glyph    = actuarianrightshort,
                    start    = 0,
                },
                {
                    advance  = basetotal,
                    ["end"]  = 0,
                    extender = 1,
                    glyph    = actuarianrightshort,
                    start    = used,
                },
            }
        }

        characters[actuarianleftlong] = {
            width    = 2*linewidth,
            height   = baseheight,
            depth    = basedepth,
            unicode  = actuarian, -- whatever
            callback = "devirtualize",
            commands = {
                rightcommand[linewidth],
                downcommand[basedepth],
                { "rule", basetotal, linewidth },
            },
            parts = {
                {
                    advance  = basetotal,
                    ["end"]  = used,
                    extender = 1,
                    glyph    = placehold,
                    start    = 0,
                },
                {
                    advance  = basetotal,
                    ["end"]  = 0,
                    glyph    = actuarianleftlong,
                    start    = used,
                },
            }
        }
        characters[actuarianbottomleftlong] =
            characters[actuarianleftlong]
        characters[actuarianleftshort] = {
            width    = 2*linewidth,
            height   = baseheight,
            depth    = basedepth,
            unicode  = actuarian, -- whatever
            callback = "devirtualize",
            commands = {
                rightcommand[linewidth],
                upcommand[baseheight-4*linewidth],
                { "rule", 4*linewidth, linewidth },
            },
            parts = {
                {
                    advance  = basetotal,
                    ["end"]  = used,
                    extender = 1,
                    glyph    = placehold,
                    start    = 0,
                },
                {
                    advance  = basetotal,
                    ["end"]  = 0,
                    glyph    = actuarianleftshort,
                    start    = used,
                },
            }
        }
        characters[actuarianbottomleftshort] = {
            width    = 2*linewidth,
         -- height   = baseheight,
            height   = linewidth,
            depth    = basedepth,
            unicode  = actuarian, -- whatever
            callback = "devirtualize",
            commands = {
                rightcommand[linewidth],
                downcommand[basedepth],
                { "rule", 4*linewidth, linewidth },
            },
            parts = {
                {
                    advance  = basetotal,
                    ["end"]  = used,
                    extender = 1,
                    glyph    = placehold,
                    start    = 0,
                },
                {
                    advance  = basetotal,
                    ["end"]  = 0,
                    glyph    = actuarianleftshort,
                    start    = used,
                },
            }
        }
        --
     -- if trace_tweaking then
     --     report_tweak("actuarian %U added",target,original,actuarian) -- wrong parameter
     -- end
    end

end

do

    -- todo: make callback because we can delay it but then we need to stack
    -- callbacks

    local nps = fonts.helpers.newprivateslot

    local list = {
     -- { 0x0300, nps("delimited right grave"),   nps("delimited ghost grave")   },
        { 0x0308, nps("delimited right ddot"),    nps("delimited ghost ddot")    },
        { 0x0304, nps("delimited right bar"),     nps("delimited ghost bar")     },
     -- { 0x0301, nps("delimited right acute"),   nps("delimited ghost acute")   },
        { 0x0302, nps("delimited right hat"),     nps("delimited ghost hat")     },
        { 0x030C, nps("delimited right check"),   nps("delimited ghost check")   },
        { 0x0306, nps("delimited right breve"),   nps("delimited ghost breve")   },
        { 0x0307, nps("delimited right dot"),     nps("delimited ghost dot")     },
        { 0x030A, nps("delimited right ring"),    nps("delimited ghost ring")    },
        { 0x0303, nps("delimited right tilde"),   nps("delimited ghost tilde")   },
        { 0x20DB, nps("delimited right dddot"),   nps("delimited ghost dddot")   },

        { 0x2032, nps("delimited right prime"),   nps("delimited ghost prime"),   false, 1 },
        { 0x2033, nps("delimited right dprime"),  nps("delimited ghost dprime"),  false, 1 },
        { 0x2034, nps("delimited right tprime"),  nps("delimited ghost tprime"),  false, 1 },
        { 0x2057, nps("delimited right qprime"),  nps("delimited ghost qprime"),  false, 1 },
        { 0x2035, nps("delimited right rprime"),  nps("delimited ghost rprime"),  false, 1 },
        { 0x2036, nps("delimited right drprime"), nps("delimited ghost rdprime"), false, 1 },
        { 0x2037, nps("delimited right dtprime"), nps("delimited ghost rtprime"), false, 1 },

        { 0x231C, nps("delimited left upper corner"),  nps("delimited ghost upper corner") },
        { 0x231D, nps("delimited right upper corner"), nps("delimited ghost upper corner") },
        { 0x231E, nps("delimited left lower corner"),  nps("delimited ghost lower corner"), true  },
        { 0x231F, nps("delimited right lower corner"), nps("delimited ghost lower corner"), true  },

        -- If needed we can have an installer:

        { 0x2020, nps("delimited right dagger"),  nps("delimited ghost dagger")  },
        { 0x2021, nps("delimited right ddagger"), nps("delimited ghost ddagger") },
        { 0x2217, nps("delimited right ast"),     nps("delimited ghost ast")     },
        { 0x22C6, nps("delimited right star"),    nps("delimited ghost star")    },

        { 0x2020, nps("delimited right dagger 1"),  nps("delimited ghost dagger 1"),  false, 1 },
        { 0x2021, nps("delimited right ddagger 1"), nps("delimited ghost ddagger 1"), false, 1 },
        { 0x2217, nps("delimited right ast 1"),     nps("delimited ghost ast 1"),     false, 1 },
        { 0x22C6, nps("delimited right star 1"),    nps("delimited ghost star 1"),    false, 1 },
    }

    function mathtweaks.addfourier(target,original,parameters)
        local characters = target.characters
        for i=1,#list do
            local entry        = list[i]
            local unicode      = entry[1]
            local basecode     = unicode
            local fouriercode  = entry[2]
            local movecode     = entry[3]
            local reverse      = entry[4]
            local size         = entry[5] or 0
            local basechar     = characters[basecode]
            local compactscale = 1
            -- bah ...
            if basechar and target.properties.compactmath and size > 0 then
                compactscale = target.parameters[size > 1 and "scriptscriptscale" or "scriptscale"] / 1000
                for i=1,size do
                    basecode = basechar.smaller or basecode
                    basechar = characters[basecode]
                end
            end
            if basechar then
                local scale   = (parameters.scale or 1) * compactscale
                local variant = parameters.variant
                if variant then
                    for i=1,variant do
                        local okay = basechar.next
                        if okay then
                            basecode = okay
                            basechar = characters[basecode]
                        else
                            break
                        end
                    end
                end
                local baseheight = scale * (basechar.height or 0)
                local basedepth  = scale * (basechar.depth  or 0)
                local basewidth  = scale * (basechar.width  or 0)
                local used       = baseheight/2
                local total      = baseheight + basedepth
                if reverse then
                    used = total / 2 -- basedepth / 2
                end
                characters[movecode] = {
                    width    = basewidth,
                    height   = used,
                    unicode  = 0xFFFD,
                    commands = {
                        downcommand[used],
                        { "rule", used, 0 },
                    },
                }
                local parts = {
                    {
                        advance  = used,
                        ["end"]  = used,
                        extender = 1,
                        glyph    = movecode, -- bottom
                        start    = used,
                    },
                    {
                        advance  = total,
                        ["end"]  = 0,
                        glyph    = fouriercode, -- top
                        start    = total,
                    },
                }
                if reverse then
                    parts[1], parts[2] = parts[2], parts[1]
                end
                characters[fouriercode] = {
                    width    = basewidth,
                    height   = baseheight, -- somehow no \primed antykwa (unless we double the height)
                    depth    = basedepth,
                    unicode  = unicode,
                    commands = {
                        scale == 1 and charcommand[basecode] or { "slot", 0, basecode, scale, scale },
                    },
                    partsorientation = "vertical",
                    parts            = parts,
--                     keepvirtual      = basechar.commands and true or false,
        --             callback = "devirtualize",
                }
                if trace_tweaking then
                    report_tweak("fourier %U added using %U",target,original,basecode,fouriercode)
                end
            end
        end
    end

end

do

    -- \im{\left\Uchar"007C \frac{1}{2} \right\Uchar"007C}
    -- \im{\left\Uchar"2016 \frac{1}{2} \right\Uchar"2016}
    -- \im{\left\Uchar"2980 \frac{1}{2} \right\Uchar"2980}

    local single <const> = 0x007C
    local double <const> = 0x2016
    local triple <const> = 0x2980

    local function variantlist(unicode,chardata,total,used)
        chardata.varianttemplate = 0x0028
     -- chardata.next = nil -- better use wipe variants but for testign we keep them
        chardata.parts = {
            {
                advance  = total,
                ["end"]  = used,
                glyph    = unicode,
                start    = 0,
            },
            {
                advance  = total,
             -- ["end"]  = 0,
             -- ["end"]  = used/5,   -- prevents small gap with inward curved endpoints
                ["end"]  = 4*used/5, -- is this better?
                extender = 1,
                glyph    = unicode,
                start    = used,
            },
        }
        chardata.partsorientation = "vertical"
    end

    -- This is such an inconsistent mess that we cannot simply copy variants so we
    -- just accept some middle ground for the missing ones. See goodie files for when
    -- 'check' and/or 'variant' is used.

    function mathtweaks.addbars(target,original,parameters)
        local characters = target.characters
        local template   = single
        local basechar   = characters[template]
        local tempchar   = basechar
        local check      = parameters.check
     -- local variant    = false -- alas ... too large: tonumber(parameters.variant)
     -- if variant then
     --     for i=1,variant do
     --         local n = tempchar.next
     --         if n then
     --             template = n
     --             tempchar = characters[template]
     --         else
     --             break
     --         end
     --     end
     -- end
        local width   = tempchar.width
        local height  = tempchar.height
        local depth   = tempchar.depth
        local advance = (parameters.advance or 1/10) * width
        local used    = 1.2*height -- large overlap because no smaller pieces
        local total   = height + depth
        --
        if not check or not basechar.parts then
            variantlist(template,basechar,total,used)
        end
        if not check or not characters[double] then
            basechar = {
                unicode  = double,
                width    = 2*width - 1*advance,
                height   = height,
                depth    = depth,
             -- callback = "devirtualize",
                commands = {
                    charcommand[template],
                    leftcommand[advance],
                    charcommand[template],
                },
            }
            characters[double] = basechar
        else
            basechar = characters[double]
        end
        if not check or not basechar.parts then
            variantlist(double,basechar,total,used)
        end
        if not check or not characters[triple] then
            basechar = {
                unicode  = triple,
                width    = 3*width - 2*advance,
                height   = height,
                depth    = depth,
             -- callback = "devirtualize",
                commands = {
                    charcommand[template],
                    leftcommand[advance],
                    charcommand[template],
                    leftcommand[advance],
                    charcommand[template],
                },
            }
            characters[triple] = basechar
        else
            basechar = characters[triple]
        end
        if not check or not basechar.parts then
            variantlist(triple,basechar,total,used)
        end
        --
        if trace_tweaking then
            report_tweak("single, double and triple bars added",target,original)
        end
    end

end

do

    -- this will move to char-prv.lmt

    local nps = fonts.helpers.newprivateslot

    local slot = nps("minus one composite")

    local data = {
        category    = "lu",
        description = "MATHEMATICAL PRE IMAGE",
        direction   = "l",
        linebreak   = "al",
        mathspec    = {
            {
                class   = mathematics.classes.prime,
                group   = "postfix operator",
                meaning = "preimage",
                name    = "preimagesymbol",
            },
            {
                class   = mathematics.classes.prime,
                group   = "postfix operator",
                meaning = "inverse",
                name    = "inversesymbol",
            },
        },
        unicode     = slot,
        recipe      = { 0x2212, 0x31 },
        specials    = { "char", 0x2212, 0x31 },
        unicodeslot = slot,
    }

    characters.data[slot] = data

    for i,d in next,data.mathspec do
        mathematics.setmathsymbol( -- could take a array
            d.name,
            d.class,
            0, -- family,
            data.unicodeslot,
            nil, -- stretch,
            d.group,
            d.meaning
        )
    end

    -- till here

    local specifications = {
        data, -- characters.data[fonts.helpers.privateslot("minus one composite")]
    }

    function mathtweaks.addcomposites(target,original,parameters)
        local characters = target.characters
        for i=1,#specifications do
            local spec     = specifications[i]
            local private  = spec.unicodeslot
            local unicode  = spec.recipe
            local commands = { }
            local width    = 0
            local height   = 0
            local depth    = 0
            for i=1,#unicode do
                local uc = unicode[i]
                local cd = characters[uc]
                local wd = cd.width or 0
                local ht = cd.height or 0
                local dp = cd.depth or 0
                commands[#commands+1] = charcommand[uc]
                width = width + wd
                if ht > height then
                    height = ht
                end
                if dp > depth then
                    depth = dp
                end
            end
            -- tounicode
            characters[private] = {
                unicode  = unicode,
                commands = commands,
                width    = width,
                height   = height,
                depth    = depth,
            }
         -- if trace_tweaking then
         --     report_tweak("composite %U added",target,original) -- useless
         -- end
        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

    function mathtweaks.sortvariants(target,original,parameters)
        local list = parameters.list
        if list then
            local characters = target.characters
            local horizontal = parameters.orientation == "horizontal"
            for i=1,#list do
                local u = list[i]
                local c = characters[u]
                if c then
                    local t = { }
                    while true do
                        local n = c.next
                        if n then
                            c = characters[n]
                        end
                        if c and not c.parts then
                            if horizontal then
                                t[c.width or 0] = n
                            else
                                t[(c.height or 0) + (c.depth or 0)] = n
                            end
                        else
                            break
                        end
                    end
                    local c = characters[u]
                    for k, v in sortedhash(t) do
                        c.next = v
                        c = characters[v]
                    end
                end
            end
        end
    end

end

do

    -- We started with the list that xits has in rtlm but most of them can be derived from
    -- the database, and others need to be added.

    -- Checked while watching/listening to Dave Matthews Band: The Central Park Concert
    -- (with superb solos by Warren Haynes), a DVD I bought around when we started with the
    -- LUATEX advanture.

    local mirrors = {

        [0x0002F] = true, -- slashes
        [0x0005C] = true,
        [0x000F7] = true,
        [0x02044] = true,
        [0x02215] = true,

        [0x02032] = true, -- primes
        [0x02033] = true,
        [0x02034] = true,
        [0x02057] = true,
        [0x02035] = true,
        [0x02036] = true,
        [0x02037] = true,

        [0x0221A] = true, -- radicals
        [0x0221B] = true,
        [0x0221C] = true,
        [0x0221D] = true,

        [0x0222B] = true, -- integrals
        [0x0222C] = true,
        [0x0222D] = true,
        [0x0222E] = true,
        [0x0222F] = true,
        [0x02230] = true,
        [0x02231] = true,
        [0x02232] = true,
        [0x02233] = true,

        [0x02A0A] = true, -- seen in xits (to be checked)
        [0x02A0B] = true,
        [0x02A0C] = true,
        [0x02A0D] = true,
        [0x02A0E] = true,

        [0x02140] = true,
        [0x02201] = true,
        [0x02202] = true,
        [0x02203] = true,
        [0x02204] = true,
        [0x02211] = true,

        [0x02239] = true,
        [0x0225F] = true,
        [0x0228C] = true,
        [0x022A7] = true,
        [0x022AA] = true,
        [0x022AC] = true,
        [0x022AD] = true,
        [0x022AE] = true,
        [0x022AF] = true,
        [0x022F5] = true,
        [0x022F8] = true,
        [0x022F9] = true,
        [0x022FF] = true,
        [0x02320] = true,
        [0x02321] = true,
        [0x027C0] = true,
        [0x029DC] = true,
        [0x029F4] = true,

        [0x02A0F] = true,
        [0x02A10] = true,
        [0x02A11] = true,
        [0x02A12] = true,
        [0x02A13] = true,
        [0x02A14] = true,
        [0x02A15] = true,
        [0x02A16] = true,
        [0x02A17] = true,
        [0x02A18] = true,
        [0x02A19] = true,
        [0x02A1A] = true,
        [0x02A1B] = true,
        [0x02A1C] = true,
        [0x02A20] = true,

        [0x02A74] = true,
        [0x02AA3] = true,
        [0x02AE2] = true,
        [0x02AE6] = true,
        [0x1D715] = true,
    }

    local new = fonts.helpers.newprivateslot

    local function add(target,original,characters,unicode,what)
        local data = characters[unicode]
        if data then
            if not data.mirror then
                local slot      = new("mirror."..unicode)
                local mirror    = copytable(data)
                data.mirror     = slot
                mirror.rorrim   = unicode -- so we can check later
                mirror.commands = {
                    { "offset", data.width, 0, unicode, -1, 1 }
                }
                if trace_tweaking then
                    report_tweak("adding mirror %U (%s)",target,original,unicode,what)
                end
                characters[slot] = mirror
            elseif trace_tweaking then
                report_tweak("skipping mirror %U (%s)",target,original,unicode,what)
            end
            local parts = data.parts
            if parts then
                for i=1,#parts do
                    add(target,original,characters,parts[i],"hpart")
                end
            end
            local smaller = data.smaller
            if smaller then
                add(target,original,characters,"smaller")
            end
            local next = data.next
            if next then
                if next == unicode then
                    if unicode == 0xFDFFF then
                        -- can happen when we add privates
                    else
                        report_tweak("skipping cyclic %U (%s)",target,original,unicode,"next")
                    end
                else
                    add(target,original,characters,next,"next")
                end
            end
        end
    end

    -- todo: also check the rtlm table if present

    function mathtweaks.addmirrors(target,original,parameters)
        local characters = target.characters
     -- for unicode, detail in sortedhash(characters) do
        for unicode, detail in next, characters do
            local data = chardata[unicode]
            if data and data.mirror then
                add(target,original,characters,unicode,"mirror")
            end
        end
        for unicode, detail in sortedhash(mirrors) do
            if characters[unicode] then
                add(target,original,characters,unicode,"character")
            elseif trace_tweaking then
                report_tweak("ignoring mirror %U (%s)",target,original,unicode,what)
            end
        end
    end

end

do

    local function check(target,original,parameters,field,what,reported)
        local metadata = original.shared.rawdata.metadata
        if metadata then
            local value    = string.strip(metadata[field] or "")
            local expected = parameters.expected
            if value and expected then
                if type(expected) ~= "table" then
                    expected = { expected }
                end
                local valid = { }
                for i=1,#expected do
                    valid[gsub(lower(tostring(expected[i] or true )),"[%s%-]","")] = true
                end
                local value    = gsub(lower(tostring(metadata[field] or false)),"[%s%-]","")
                local message  = parameters.message
                local hasissue = not valid[value] and not reported[value]
                if hasissue then
                    report_tweak("%s %a found, %s one of [% | t] expected",target,original,what,value,what,expected)
                elseif trace_tweaking then
                    report_tweak("%s %a found",target,original,what,value)
                end
                if message and message ~= "" and not reported[value] then
                    report_tweak()
                    report_tweak("%s",target,original,message)
                    report_tweak()
                end
                reported[value] = true
                if hasissue then
                    return value, expected and concat(expected," | ") or "?"
                end
            end
        end
    end

    local reported = { }

    function mathtweaks.version(target,original,parameters)
        check(target,original,parameters,"version","version",reported)
    end

    local reported = { }

    function mathtweaks.fontname(target,original,parameters)
        local bad, good = check(target,original,parameters,"fontname","fontname",reported)
        if bad and good and parameters.abort then
         -- environment.arguments.signal = "qr"
            tex.error(
                'We have a font issue!',
                'Are you sure that you use the right math font file? I expected\n' ..
                '"' .. good .. '" and not "' .. bad .. '"\n' ..
                'so you might consider contacting those responsible for the\n' ..
                'distribution that you use.'
            )
        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.scales(target,original,parameters)
--         local fontparameters = target.parameters
--         if fontparameters then
--             fontparameters.scriptxscale       = parameters.scriptxscale
--             fontparameters.scriptyscale       = parameters.scriptyscale
--             fontparameters.scriptscriptxscale = parameters.scriptscriptxscale
--             fontparameters.scriptscriptyscale = parameters.scriptscriptyscale
--             target.scriptxscale       = parameters.scriptxscale
--             target.scriptyscale       = parameters.scriptyscale
--             target.scriptscriptxscale = parameters.scriptscriptxscale
--             target.scriptscriptyscale = parameters.scriptscriptyscale
--         end
--     end
--
-- end

-- do
--
--     function mathtweaks.diagnose(target,original,parameters)
--         local characters = target.characters
--         for k, v in sortedhash(characters) do
--             local italic = v.italic
--             if italic then
--                 report_tweak("italics: %C %p",target,original,k,italic)
--             end
--         end
--     end
--
-- end

do

    function mathtweaks.setoptions(target,original,parameters)
        local setlist   = parameters.set or parameters.list
        local resetlist = parameters.reset
        if setlist or resetlist then
            local properties = target.properties
            local codes      = tex.mathcontrolcodes
            local oldcontrol = texget("mathfontcontrol")
            local newcontrol = oldcontrol
            -- todo: reset
            if resetlist then
                for i=1,#resetlist do
                    local v = tonumber(codes[resetlist[i]])
                    if v then
                        newcontrol = newcontrol & (not v)
                    end
                end
            end
            if setlist then
                for i=1,#setlist do
                    local v = tonumber(codes[setlist[i]])
                    if v then
                        newcontrol = newcontrol | v
                    end
                end
            end
            newcontrol = newcontrol | codes.usefontcontrol
            properties.mathcontrol = newcontrol
            target.mathcontrol = newcontrol
            if trace_tweaking then
                report_tweak("forcing math font options 0x%08X instead of 0x%08X",target,original,newcontrol,oldcontrol)
            end
        end
    end

end

do

    function mathtweaks.setovershoots(target,original,parameters)
        local list = parameters.list
        if list then
            local characters = target.characters
            local emwidth    = target.parameters.quad
            local done       = false
            for i=1,#list do
                local entry  = list[i]
                local target = entry.target
                local top    = entry.topovershoot
                local quad   = entry.quad
                if target and top then
                    local range = blocks[target]
                    if range then
                        if quad then
                            quad = emwidth
                        end
                        for r = range.first, range.last do
                            local unicode = mathgaps[r] or r
                            local data = characters[unicode]
                            if data then
                                data.topovershoot = top * (quad or data.width or 0)
                                done = registerdone(done,r)
                            end
                        end
                    end
                end
            end
            feedback_tweak("setovershoots",target,original,done)
        end
    end

    -- there is no real need for this but let's play nice with memory anyway

    local efindex = 0
    local effects = setmetatableindex (function (t,k)
        efindex = efindex + 1
        local v = "tweakreplacealphabets" .. efindex
        local e = fonts.specifiers.presetcontext(v,"",k)
     -- print(k,v,e)
        t[k] = v
        return v
    end)

    function mathtweaks.replacealphabets(target,original,parameters)
        local list = parameters.list
        if list then
            local features        = target.specification.features.normal
            local definedfont     = fonts.definers.internal
            local copiedglyph     = fonts.handlers.vf.math.copy_glyph
            -- does a deep copy, including parts and so
            local getsubstitution = fonts.handlers.otf.getsubstitution
            local fontdata        = fonts.hashes.identifiers
            --
            local fonts      = target.fonts
            local size       = target.size
            local characters = target.characters
            if not fonts then
                fonts = { }
                target.fonts = fonts
            end
            if #fonts == 0 then
                fonts[1] = { id = 0, size = size } -- self, will be resolved later
            end
            for i=1,#list do
                local entry      = list[i]
                local filename   = entry.filename or parameters.filename
                local feature    = entry.feature
                local thesource  = entry.source
                local thetarget  = entry.target or thesource
                local theunicode = entry.unicode
                local unicodes   = entry.unicodes
                local keep       = (entry.keep == true) or (parameters.keep == true)
                if (thesource and thetarget) or unicodes then
                    local function getrange(t)
                        return (type(t) == "table" and t) or (type(t) == "string" and blocks[t])  -- .gaps
                    end
                    local sourcerange  = thesource   and getrange(thesource)
                    local targetrange  = thetarget   and getrange(thetarget)
                    local unicoderange = theunicode  and getrange(theunicode)
                    local firsttarget  = targetrange and targetrange.first
                    local firstsource  = sourcerange and sourcerange.first
                    local lastsource   = sourcerange and sourcerange.last or firstsource
                    if (firstsource and firsttarget) or unicodes then
                        local offset = unicodes and 0 or (firsttarget - firstsource)
                        if filename then
                            local rscale = entry.rscale or 1 -- todo
                            size = size * rscale -- maybe use scale in vf command
                            -- load font, todo: set language and script, the effect hack is ugly
                            local fullname = filename
                            local effect = features.effect
                            if effect then
                                fullname = fullname .. "*" .. effects["effect={"..effect.."}"]
                            end
                            local id = definedfont {
                                name = fullname,
                                size = size,
                            }
                            if id <= 0 then
                                report_math("font %a doesn't exist",fullname)
                            else
                                local chars  = fontchars[id]
                                local dropin = fontdata[id]
                                local index  = false
                                for i=1,#fonts do
                                    local f = fonts[i]
                                    if f.id == id and f.size == size then
                                        index = i
                                        break
                                    end
                                end
                                if not index then
                                    index = #fonts + 1
                                    fonts[index] = { id = id, size = size }
                                end
                                -- copy characters
                                local function use(sourceunicode,targetunicode)
                                    if keep and characters[targetunicode] then
                                        -- okay
                                    else
                                        if feature then
                                            sourceunicode = getsubstitution(dropin,sourceunicode,feature,true,"math","dflt") or sourceunicode
                                        end
                                        characters[targetunicode] = copiedglyph(target,characters,chars,sourceunicode,index)
                                    end
                                end
                                if unicodes then
                                    for i=1,#unicodes do
                                        local unicode = unicodes[i]
                                        if chars[unicode] then
                                            use(unicode,unicode)
                                        end
                                    end
                                else
                                    for s=firstsource,lastsource do
                                        local t = s + offset
                                        local sourceunicode = mathgaps[s] or s
                                        if chars[sourceunicode] then
                                            use(sourceunicode,mathgaps[t] or t)
                                        end
                                    end
                                end
                                --
                                local inherit = entry.inherit
                                if inherit then
                                    local mathparameters = target.mathparameters
                                    local dropparameters = fontdata[id].mathparameters
                                    if dropparameters then
                                        for name in sortedhash(inherit) do
                                            local value = dropparameters[name]
                                            if value then
                                                mathparameters[name] = value
                                            end
                                        end
                                    end
                                end
                            end
                        elseif feature then
                            local function use(sourceunicode,targetunicode)
                                local variant = getsubstitution(original,sourceunicode,feature,true,"math","dflt")
                                local data    = characters[variant]
                                if data then
                                    characters[targetunicode] = copytable(data)
                                end
                            end
                            if unicodes then
                                for i=1,#unicodes do
                                    local unicode = unicodes[i]
                                    use(unicode,unicode)
                                end
                            else
                                for s=firstsource,lastsource do
                                    local t = s + offset
                                    use(mathgaps[s] or s,mathgaps[t] or t)
                                end
                            end
                        else
                            -- this is a hack for stix providing alphabets without proper unicodes
                            if unicodes then
                                -- not supported
                            else
                                local firstcode    = unicoderange and unicoderange.first
                                local descriptions = original.descriptions
                                for s=firstsource,lastsource do
                                    local t = s + offset
                                    local sourceunicode = mathgaps[s] or s
                                    local targetunicode = mathgaps[t] or t
                                    if sourceunicode ~= targetunicode then
                                        local data = characters[sourceunicode]
                                        if data then
                                            if firstcode then
                                                local d = descriptions[sourceunicode]
                                                if d then
                                                    d.unicode = firstcode -- wins
                                                end
                                                data.unicode = firstcode
                                                firstcode = firstcode + 1
                                            end
                                            characters[targetunicode] = copytable(data)
                                        end
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end
    end

    function mathtweaks.fallbacks(target,original,parameters)
        local fallbacks = target.specification.fallbacks
        if fallbacks then
            local definitions = fonts.collections.definitions[fallbacks]
            if definitions then
                local list = { }
                for i=1,#definitions do
                    local definition = definitions[i]
                 -- local check  = definition.check
                 -- local force  = definition.force
                    local first  = definition.start
                    local last   = definition.stop
                    local offset = definition.offset or first
                    list[#list+1] = {
                        filename = definition.font,
                        rscale   = definition.rscale or 1,
                        source   = { first = first,  last = last },
                        target   = { first = offset, last = offset + (last - first) },
                    }
                end
                mathtweaks.replacealphabets(target,original,{
                    tweak = "replacealphabets",
                    list  = list,
                } )
            end
        end
    end

end

local apply_tweaks   = true  directives.register("math.applytweaks", function(v) apply_tweaks = v end)
local applied_tweaks = 0

local function tweaklist(target,original,tweaks)
    if type(tweaks) == "table" then
        for i=1,#tweaks do
            local tweak  = tweaks[i]
            if type(tweak) == "table" then
                local action = mathtweaks[tweak.tweak or ""]
                if action then
                    local feature  = tweak.feature
                    local features = target.specification.features.normal
                    if feature == nil or features[feature] then
                        local version = tweak.version
                        if version and version ~= target.tweakversion then
                            report_math("skipping tweak %a version %a",tweak.tweak,version)
                        elseif original then
                            action(target,original,tweak)
                        else
                            action(target,tweak)
                        end
                    end
                end
            end
        end
    end
end

function mathtweaks.tweaks(target,original,parameters)
    tweaklist(target,original,parameters.list)
end

local function applytweaks(when,target,original)
    if apply_tweaks then
        local goodies = original.goodies
        if goodies then
            local tweaked = target.tweaked or { }
            if tweaked[when] then
                if trace_defining then
                    report_math("tweaking math of %a @ %p (%s: %s)",target.properties.fullname,target.parameters.size,when,"done")
                end
            else
                for i=1,#goodies do
                    local goodie = goodies[i]
                    local mathematics = goodie.mathematics
                    local tweaks = mathematics and mathematics.tweaks
                    if type(tweaks) == "table" then
                        statistics.starttiming(mathtweaks)
                        applied_tweaks = applied_tweaks + 1
                        tweaks = tweaks[when]
                        if trace_defining then
                            report_math("tweaking math of %a @ %p (%s: %s)",target.properties.fullname,target.parameters.size,when,"okay")
                        end
                        tweaklist(target,original,tweaks)
                    end
                    statistics.stoptiming(mathtweaks)
                end
                tweaked[when] = true
                target.tweaked = tweaked
            end
        end
    else
        report_math("not tweaking math of %a @ %p (%s)",target.properties.fullname,target.parameters.size,when)
    end
end

local function tweakable(target)
    local mathparameters = target.mathparameters
--     local features       = target.specification.features
--     local mathscript     = features and features.normal and features.normal.script == "math"
--     return mathparameters and mathscript -- and target,properties.hasmath
    return mathparameters
end

function mathematics.tweakbeforecopyingfont(target,original)
    if use_math_goodies and tweakable(target) then
        applytweaks("beforecopying",target,original)
    end
end

function mathematics.tweakaftercopyingfont(target,original)
    if use_math_goodies and tweakable(target) then
        applytweaks("aftercopying",target,original)
        target.properties.hasitalics = false
    end
end

statistics.register("math tweaking time",function()
    if applied_tweaks > 0 then
        return string.format("%s seconds, %s math goodie tables", statistics.elapsedtime(mathtweaks),applied_tweaks)
    end
end)

do

    local defaults = {
        {
            source = "uppercasescript",
            target = "uppercasecalligraphic",
        },
        {
            source = "lowercasescript",
            target = "lowercasecalligraphic",
        },
        {
            source = "uppercaseboldscript",
            target = "uppercaseboldcalligraphic",
        },
        {
            source = "lowercaseboldscript",
            target = "lowercaseboldcalligraphic",
        },
    }

    local reported = table.setmetatableindex("table")

    function mathematics.checkaftercopyingfont(target,original)
        if tweakable(target) then
            local chardata   = characters.data
            local characters = target.characters
            --
            for i=1,#defaults do
                -- we assume no ssty here yet .. todo
                local default = defaults[i]
                local block   = blocks[default.target]
                local first   = block.first
                local last    = block.last
                if not characters[mathgaps[first] or last] then
                    mathtweaks.replacealphabets(target,original,{
                        tweak   = "replacealphabets",
                        list    = { default }
                    })
                end
            end
            --
            local addvariant = mathematics.addvariant
            local function register(old,new)
                for i, cold in next, old do
                    local cnew = new[i]
                    addvariant(target,cold,cold,0xFE00)
                    addvariant(target,cnew,cnew,0xFE01)
                    addvariant(target,cnew,cold,0xFE00)
                    addvariant(target,cold,cnew,0xFE01)
                end
            end
            local sr = mathematics.alphabets.sr.tf
            local ca = mathematics.alphabets.ca.tf
            register(sr.ucletters,ca.ucletters)
            register(sr.lcletters,ca.lcletters)
            --
            if checkitalics then
                local italics  = 0
                local metadata = original.shared.rawdata.metadata
                local fontname = metadata and metadata.fontname or false
             -- for k, v in sortedhash(characters) do
                for k, v in next, characters do
                    local italic = v.italic
                    if italic then
                        local unicode = v.unicode
                        if unicode and not reported[fontname][unicode] then -- there can be variants
                            local data        = chardata[unicode]
                            local description = data.description or ""
                            local category    = data.category or "--"
                            report_tweak("italics: %C %p %s %s",target,original,k,italic,category,description)
                            reported[fontname][unicode] = true
                        end
                        italics = italics + 1
                    end
                end
                if italics > 0 then
                    report_tweak("still has %i italics",target,original,italics)
                    goto NEXTSTEP
                end
            end
          ::NEXTSTEP::
            -- more to come
        end
    end

end

function mathematics.beforepassingfonttotex(target,original)
    if tweakable(target) then
        applytweaks("beforepassing",target,original)
    end
end

sequencers.appendaction("mathparameters","system","mathematics.overloadparameters")
sequencers.appendaction("mathparameters","system","mathematics.scaleparameters")

sequencers.appendaction("beforecopyingcharacters","system","mathematics.tweakbeforecopyingfont")
sequencers.appendaction("aftercopyingcharacters", "system","mathematics.tweakaftercopyingfont")
sequencers.appendaction("aftercopyingcharacters", "system","mathematics.checkaftercopyingfont")
sequencers.appendaction("beforepassingfonttotex", "system","mathematics.beforepassingfonttotex")

-- no, it's a feature now (see good-mth):
--
-- sequencers.appendaction("aftercopyingcharacters","system","mathematics.overloaddimensions")

-- we use numbers at the tex end (otherwise we could stick to chars)

local e_left       = extensibles.left
local e_right      = extensibles.right
local e_horizontal = extensibles.horizontal
local e_mixed      = extensibles.mixed
local e_unknown    = extensibles.unknown

local unknown      = { e_unknown, false, false }

-- top curly bracket: 23DE

local function extensiblecode(font,unicode)
    local characters = fontcharacters[font]
    local character = characters[unicode]
    if not character then
        return unknown
    end
    local first = character.next
    local code = unicode
    local next = first
    while next do
        code = next
        character = characters[next]
        next = character.next
    end
    local char = chardata[unicode]
    if not char then
        return unknown
    end
    if character.parts then
        local m = char.mathextensible
        local e = m and extensibles[m]
        return e and { e, code, character } or unknown
    elseif first then
        -- assume accent (they seldom stretch .. sizes)
        local m = char.mathextensible or char.mathstretch
        local e = m and extensibles[m]
        return e and { e, code, character } or unknown
    else
        return unknown
    end
end

setmetatableindex(extensibles,function(extensibles,font)
    local codes = { }
    setmetatableindex(codes, function(codes,unicode)
        local status = extensiblecode(font,unicode)
        codes[unicode] = status
        return status
    end)
    extensibles[font] = codes
    return codes
end)

local function extensiblecode(family,unicode)
    return extensibles[getfontoffamily(family or 0)][unicode][1]
end

-- left       : [head] ...
-- right      : ... [head]
-- horizontal : [head] ... [head]
--
-- abs(right["start"] - right["end"]) | right.advance | characters[right.glyph].width

local function horizontalcode(family,unicode)
    local font    = getfontoffamily(family or 0)
    local data    = extensibles[font][unicode]
    local kind    = data[1]
    local loffset = 0
    local roffset = 0
    if kind == e_left then
        local charlist = data[3].parts
        if charlist then
            local left = charlist[1]
            loffset = abs((left["start"] or 0) - (left["end"] or 0))
        end
    elseif kind == e_right then
        local charlist = data[3].parts
        if charlist then
            local right = charlist[#charlist]
            roffset = abs((right["start"] or 0) - (right["end"] or 0))
        end
     elseif kind == e_horizontal then
        local charlist = data[3].parts
        if charlist then
            local left  = charlist[1]
            local right = charlist[#charlist]
            loffset = abs((left ["start"] or 0) - (left ["end"] or 0))
            roffset = abs((right["start"] or 0) - (right["end"] or 0))
        end
    end
    return kind, loffset, roffset
end

mathematics.extensiblecode = extensiblecode
mathematics.horizontalcode = horizontalcode

-- no longer needed:

interfaces.implement {
    name      = "extensiblecode",
    arguments = "2 integers",
 -- usage     = "value", -- maybe
    public    = true,
    actions   = { extensiblecode, context }
}

interfaces.implement {
    name      = "mathhorizontalcode",
    arguments = "2 integers",
 -- usage     = "value", -- maybe
    public    = true,
    actions   = function(family,unicode)
        local kind, loffset, roffset = horizontalcode(family,unicode)
        texsetdimen(d_scratchleftoffset, loffset)
        texsetdimen(d_scratchrightoffset,roffset)
        context(kind)
    end
}

function mathematics.variantcode(unicode,variant)
    local data = fontcharacters[getfontoffamily(texget("fam"))]
    local char = data and data[unicode]
    if char then
        for i=1,variant do
            local next = char.next
            if next then
                unicode = next
                char = data[next]
            else
                break
            end
        end
    end
    return unicode
end

function mathematics.variantcount(unicode)
    local data  = fontcharacters[getfontoffamily(texget("fam"))]
    local char  = data and data[unicode]
    local count = 0
    if char then
        while true do
            local next = char.next
            if next then
                count = count + 1
                char = data[next]
            else
                break
            end
        end
    end
    return count
end

interfaces.implement {
    name      = "mathvariantcode",
    public    = true,
    arguments = "2 integers",
    actions   = { mathematics.variantcode, context },
}

interfaces.implement {
    name      = "mathvariantcount",
    public    = true,
    arguments = "integer",
    actions   = { mathematics.variantcount, context },
}
