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

-- some names of table entries will be changed (no _)

local next, tostring, tonumber, rawget = next, tostring, tonumber, rawget
local format, match, lower, gsub, find = string.format, string.match, string.lower, string.gsub, string.find
local sort, insert, concat = table.sort, table.insert, table.concat
local sortedkeys, sortedhash, serialize, fastcopy = table.sortedkeys, table.sortedhash, table.serialize, table.fastcopy
local derivetable = table.derive
local ioflush = io.flush
local round = math.round
local setmetatable, getmetatable, rawget, rawset = setmetatable, getmetatable, rawget, rawset

local trace_defining  = false  trackers.register("fonts.defining",  function(v) trace_defining = v end)
local trace_scaling   = false  trackers.register("fonts.scaling",   function(v) trace_scaling  = v end)

local report_defining = logs.reporter("fonts","defining")

-- Watch out: no negative depths and negative heights are permitted in regular
-- fonts. Also, the code in LMTX is a bit different. Here we only implement a
-- few helper functions.

local fonts                  = fonts
local constructors           = fonts.constructors or { }
fonts.constructors           = constructors
local handlers               = fonts.handlers or { } -- can have preloaded tables
fonts.handlers               = handlers

local allocate               = utilities.storage.allocate
local setmetatableindex      = table.setmetatableindex

-- will be directives

constructors.version         = 1.01
constructors.cache           = containers.define("fonts", "constructors", constructors.version, false)

constructors.privateoffset   = fonts.privateoffsets.textbase or 0xF0000

-- This might become an interface:

local designsizes           = allocate()
constructors.designsizes    = designsizes
local loadedfonts           = allocate()
constructors.loadedfonts    = loadedfonts

----- scalecommands         = fonts.helpers.scalecommands

-- We need to normalize the scale factor (in scaled points). This has to do with the
-- fact that TeX uses a negative multiple of 1000 as a signal for a font scaled
-- based on the design size.

local factors = {
    pt = 65536.0,
    bp = 65781.8,
}

function constructors.setfactor(f)
    constructors.factor = factors[f or 'pt'] or factors.pt
end

constructors.setfactor()

function constructors.scaled(scaledpoints, designsize) -- handles designsize in sp as well
    if scaledpoints < 0 then
        local factor = constructors.factor
        if designsize then
            if designsize > factor then -- or just 1000 / when? mp?
                return (- scaledpoints/1000) * designsize -- sp's
            else
                return (- scaledpoints/1000) * designsize * factor
            end
        else
            return (- scaledpoints/1000) * 10 * factor
        end
    else
        return scaledpoints
    end
end

function constructors.getprivate(tfmdata)
    local properties = tfmdata.properties
    local private = properties.private
    properties.private = private + 1
    return private
end

function constructors.setmathparameter(tfmdata,name,value)
    local m = tfmdata.mathparameters
    local c = tfmdata.MathConstants
    if m then
        m[name] = value
    end
    if c and c ~= m then
        c[name] = value
    end
end

function constructors.getmathparameter(tfmdata,name)
    local p = tfmdata.mathparameters or tfmdata.MathConstants
    if p then
        return p[name]
    end
end

-- Beware, the boundingbox is passed as reference so we may not overwrite it in the
-- process; numbers are of course copies. Here 65536 equals 1pt. (Due to excessive
-- memory usage in CJK fonts, we no longer pass the boundingbox.)
--
-- The scaler is only used for OTF and AFM and virtual fonts. If a virtual font has
-- italic correction make sure to set the hasitalics flag. Some more flags will be
-- added in the future.
--
-- The reason why the scaler was originally split, is that for a while we
-- experimented with a helper function. However, in practice the API calls are too
-- slow to make this profitable and the Lua based variant was just faster. A days
-- wasted day but an experience richer.

-- experimental, sharing kerns (unscaled and scaled) saves memory
-- local sharedkerns, basekerns = constructors.check_base_kerns(tfmdata)
-- loop over descriptions (afm and otf have descriptions, tfm not)
-- there is no need (yet) to assign a value to chr.tonunicode
--
-- constructors.prepare_base_kerns(tfmdata) -- optimalization
--
-- We have target.name = metricfile and target.fullname = RealName and
-- target.filename = diskfilename when collapsing fonts. LuaTeX looks at both
-- target.name and target.fullname because TTC files can have multiple subfonts.

function constructors.calculatescale(tfmdata,scaledpoints)
    -- implemented in font-ctx.lmt
end

function constructors.assignmathparameters(target,original)
    -- implemented in math-act.lua
end

function constructors.beforecopyingcharacters(target,original)
    -- can be used for additional tweaking
end

function constructors.aftercopyingcharacters(target,original)
    -- can be used for additional tweaking
end

function constructors.beforepassingfonttotex(target,tfmdata)
    -- can be used for additional tweaking
end

function constructors.trytosharefont(target,tfmdata)
    -- implemented in font-def.lmt
end

local synonyms = {
    exheight      = "xheight",
    xheight       = "xheight",
    ex            = "xheight",
    emwidth       = "quad",
    em            = "quad",
    spacestretch  = "spacestretch",
    stretch       = "spacestretch",
    spaceshrink   = "spaceshrink",
    shrink        = "spaceshrink",
    extraspace    = "extraspace",
    xspace        = "extraspace",
    slantperpoint = "slant",
}

function constructors.enhanceparameters(parameters)
    local mt = getmetatable(parameters)
    local getter = function(t,k)
        if not k then
            return nil
        end
        local s = synonyms[k]
        if s then
            return rawget(t,s) or (mt and mt[s]) or nil
        end
        if k == "spacing" then
            return {
                width   = t.space,
                stretch = t.spacestretch,
                shrink  = t.spaceshrink,
                extra   = t.extraspace,
            }
        end
        return mt and mt[k] or nil
    end
    local setter = function(t,k,v)
        if not k then
            return 0
        end
        local s = synonyms[k]
        if s then
            rawset(t,s,v)
        elseif k == "spacing" then
            if type(v) == "table" then
                rawset(t,"space",v.width or 0)
                rawset(t,"spacestretch",v.stretch or 0)
                rawset(t,"spaceshrink",v.shrink or 0)
                rawset(t,"extraspace",v.extra or 0)
            end
        else
            rawset(t,k,v)
        end
    end
    setmetatable(parameters, {
        __index    = getter,
        __newindex = setter,
    })
end

local function mathkerns(v,vdelta)
    local k = { }
    for i=1,#v do
        local entry  = v[i]
        local height = entry.height
        local kern   = entry.kern
        k[i] = {
            height = height and vdelta*height or 0,
            kern   = kern   and vdelta*kern   or 0,
        }
    end
    return k
end

local psfake = 0

local function fixedpsname(psname,fallback)
    local usedname = psname
    if psname and psname ~= "" then
        if find(psname," ",1,true) then
            usedname = gsub(psname,"[%s]+","-")
        else
            -- we assume that the name is sane enough (we might sanitize completely some day)
        end
    elseif not fallback or fallback == "" then
        psfake = psfake + 1
        psname = "fakename-" .. psfake
    else
        -- filenames can be a mess so we do a drastic cleanup
        psname   = fallback
        usedname = gsub(psname,"[^a-zA-Z0-9]+","-")
    end
    return usedname, psname ~= usedname
end

local function scaleparts(parts,delta)
    local t = { }
    for i=1,#parts do
        local p = parts[i]
        local s = p["start"]   or 0
        local e = p["end"]     or 0
        local a = p["advance"] or 0
        t[i] = {
            ["start"]    = s == 0 and 0 or s * delta,
            ["end"]      = e == 0 and 0 or e * delta,
            ["advance"]  = a == 0 and 0 or a * delta,
            ["extender"] = p["extender"],
            ["glyph"]    = p["glyph"],
        }
    end
    return t
end

local abovebaseline_tag <const> = tex.charactertagcodes.abovebaseline
local belowbaseline_tag <const> = tex.charactertagcodes.belowbaseline

local textcontrolcodes  = tex.textcontrolcodes
local collapse_hyphens  <const> = textcontrolcodes.collapsehyphens
local base_ligaturing   <const> = textcontrolcodes.baseligaturing
local base_kerning      <const> = textcontrolcodes.basekerning
local none_protected    <const> = textcontrolcodes.noneprotected
local has_italics       <const> = textcontrolcodes.hasitalics
local auto_italics      <const> = textcontrolcodes.autoitalics

function constructors.scale(tfmdata,specification)
    local target         = { } -- the new table
    --
    if tonumber(specification) then
        specification    = { size = specification }
    end
    target.specification = specification
    --
    local scalecommands  = fonts.helpers.scalecommands -- defined later
    --
    local scaledpoints   = specification.size
    local relativeid     = specification.relativeid
    --
    local properties     = tfmdata.properties     or { }
    local goodies        = tfmdata.goodies        or { }
    local resources      = tfmdata.resources      or { }
    local descriptions   = tfmdata.descriptions   or { } -- bad news if empty
    local characters     = tfmdata.characters     or { } -- bad news if empty
    local changed        = tfmdata.changed        or { } -- for base mode
    local shared         = tfmdata.shared         or { }
    local parameters     = tfmdata.parameters     or { }
    local mathparameters = tfmdata.mathparameters or { }
    --
    local targetcharacters     = { }
    local targetdescriptions   = derivetable(descriptions)
    local targetparameters     = derivetable(parameters)
    local targetproperties     = derivetable(properties)
    local targetgoodies        = goodies                        -- we need to loop so no metatable
    target.characters          = targetcharacters
    target.descriptions        = targetdescriptions
    target.parameters          = targetparameters
 -- target.mathparameters      = targetmathparameters           -- happens elsewhere
    target.properties          = targetproperties
    target.goodies             = targetgoodies
    target.shared              = shared
    target.resources           = resources
    target.unscaled            = tfmdata                        -- the original unscaled one
    --
    -- specification.mathsize : 1=text 2=script 3=scriptscript
    -- specification.textsize : natural (text)size
    -- parameters.mathsize    : 1=text 2=script 3=scriptscript >1000 enforced size (feature value other than yes)
    --
    local mathsize    = tonumber(specification.mathsize) or 0
    local textsize    = tonumber(specification.textsize) or scaledpoints
 -- local forcedsize  = tonumber(parameters.mathsize   ) or 0 -- can be set by the feature "mathsize"
    local extrafactor = tonumber(specification.factor  ) or 1
 --    if (mathsize == 2 or forcedsize == 2) and parameters.scriptpercentage then
 --        scaledpoints = parameters.scriptpercentage * textsize / 100
 --    elseif (mathsize == 3 or forcedsize == 3) and parameters.scriptscriptpercentage then
 --        scaledpoints = parameters.scriptscriptpercentage * textsize / 100
 --    elseif forcedsize > 1000 then -- safeguard
 --        scaledpoints = forcedsize
 --    else
 --        -- in context x and xx also use mathsize
 --    end
    targetparameters.mathsize    = mathsize
    targetparameters.textsize    = textsize
 -- targetparameters.forcedsize  = forcedsize
    targetparameters.extrafactor = extrafactor
    --
    local defaultwidth  = resources.defaultwidth  or 0
    local defaultheight = resources.defaultheight or 0
    local defaultdepth  = resources.defaultdepth or 0
    local units         = parameters.units or 1000
    --
    -- boundary keys are no longer needed as we now have a string 'right_boundary'
    -- that can be used in relevant tables (kerns and ligatures) ... not that I ever
    -- used them
    --
 -- boundarychar_label = 0,     -- not needed
 -- boundarychar       = 65536, -- there is now a string 'right_boundary'
 -- false_boundarychar = 65536, -- produces invalid tfm in luatex
    --
    targetproperties.language   = properties.language or "dflt" -- inherited
    targetproperties.script     = properties.script   or "dflt" -- inherited
    targetproperties.mode       = properties.mode     or "base" -- inherited
    targetproperties.method     = properties.method
    --
    local askedscaledpoints   = scaledpoints
    local scaledpoints, delta = constructors.calculatescale(tfmdata,scaledpoints,nil,specification) -- no shortcut, dan be redefined
-- print("B",mathsize,askedscaledpoints,scaledpoints,delta)
    --
-- delta = round(delta) -- TODO
    --
    local hdelta      = delta
    local vdelta      = delta
    --
    target.designsize = parameters.designsize -- not really needed so it might become obsolete
    target.units      = units
    --
    target.size       = scaledpoints
    --
    target.subfont    = properties.subfont
    target.cidinfo    = properties.cidinfo
    target.format     = properties.format
    --
    local original = properties.original or tfmdata.original
    local fontname = properties.fontname or tfmdata.fontname
    local fullname = properties.fullname or tfmdata.fullname
    local filename = properties.filename or tfmdata.filename
    local psname   = properties.psname   or tfmdata.psname
    local name     = properties.name     or tfmdata.name
    --
    -- The psname used in pdf file as well as for selecting subfont in ttc although
    -- we don't need that subfont look up here (mapfile stuff).
    --
    local psname, psfixed = fixedpsname(psname,fontname or fullname or file.nameonly(filename))
    --
    target.original = original
    target.fontname = fontname
    target.fullname = fullname
    target.filename = filename
    target.psname   = psname
    target.name     = name
    --
    properties.fontname = fontname
    properties.fullname = fullname
    properties.filename = filename
    properties.psname   = psname
    properties.name     = name
    -- expansion (hz)
    local expansion = parameters.expansion
    if expansion then
        target.stretch = expansion.stretch
        target.shrink  = expansion.shrink
        target.step    = expansion.step
    end
    -- The official entries are in the effects subtable. Later we will set the
    -- values in the parameters table because the backend needs these.
    local effect = properties.effect
    if effect then -- units ?
        local slant = effect.slant or 0
        if slant ~= 0 then
            target.slant = slant * 1000
         -- italic : new wd = oldwd + slant * ht
        else
            target.slant = 0
        end
        local extend = effect.extend or 1
        if extend ~= 0 and extend ~= 1 then
            hdelta = hdelta * extend
            target.extend = extend * 1000
         -- target.extend = round(target.extend)
        else
            target.extend = 1000
        end
        local squeeze = effect.squeeze or 1
        if squeeze ~= 0 and squeeze ~= 1 then
            vdelta = vdelta * squeeze
            target.squeeze = squeeze * 1000
         -- target.squeeze = round(target.squeeze)
        else
            target.squeeze = 1000
        end
        local mode = effect.mode or 0
        if mode ~= 0 then
            target.mode = mode
        end
        -- target height !
        local width = effect.width or 0
        if width ~= 0 then
            target.width = width * 1000
        end
    end
    --
    targetproperties.effect       = effect
    targetparameters.factor       = delta
    targetparameters.hfactor      = hdelta
    targetparameters.vfactor      = vdelta
    targetparameters.size         = scaledpoints
    targetparameters.units        = units
    targetparameters.scaledpoints = askedscaledpoints
    targetparameters.mode         = mode
    targetparameters.width        = width
    --
    local hasquality       = parameters.expansion or parameters.protrusion
    local hasitalics       = properties.hasitalics
    local useditalicangle  = properties.useditalicangle or properties.italicangle or 0
    local usedslant        = properties.usedslant       or properties.usedslant   or 0
 -- local stackmath        = not properties.nostackmath
    local haskerns         = properties.haskerns     or properties.mode == "base" -- we can have afm in node mode
    local hasligatures     = properties.hasligatures or properties.mode == "base" -- we can have afm in node mode
    local writingmode      = properties.writingmode or "horizontal"
    --
    targetparameters.useditalicangle = useditalicangle
    targetproperties.useditalicangle = useditalicangle
    targetparameters.usedslant       = usedslant
    targetproperties.usedslant       = usedslant
    --
    if changed and not next(changed) then
        changed = false
    end
    --
    target.writingmode = writingmode == "vertical" and "vertical" or "horizontal"
    --
    target.postprocessors = tfmdata.postprocessors
    --
    local targetslant        = (parameters.slant        or parameters[1] or 0) * factors.pt -- per point
    local targetspace        = (parameters.space        or parameters[2] or 0) * hdelta
    local targetspacestretch = (parameters.spacestretch or parameters[3] or 0) * hdelta
    local targetspaceshrink  = (parameters.spaceshrink  or parameters[4] or 0) * hdelta
    local targetxheight      = (parameters.xheight      or parameters[5] or 0) * vdelta
    local targetquad         = (parameters.quad         or parameters[6] or 0) * hdelta
    local targetextraspace   = (parameters.extraspace   or parameters[7] or 0) * hdelta
    --
    targetparameters.expansion    = parameters.expansion
    targetparameters.protrusion   = parameters.protrusion
    --
    targetparameters.slant        = targetslant -- slantperpoint
    targetparameters.space        = targetspace
    targetparameters.spacestretch = targetspacestretch
    targetparameters.spaceshrink  = targetspaceshrink
    targetparameters.xheight      = targetxheight
    targetparameters.quad         = targetquad
    targetparameters.extraspace   = targetextraspace
    --
    local hshift = parameters.hshift
    if hshift then
        targetparameters.hshift = delta * hshift
    end
    local vshift = parameters.vshift
    if vshift then
        targetparameters.vshift = delta * vshift
    end
    --
    local ascender  = parameters.ascender  if ascender  then targetparameters.ascender  = vdelta * ascender  end
    local descender = parameters.descender if descender then targetparameters.descender = vdelta * descender end
    local capheight = parameters.capheight if capheight then targetparameters.capheight = vdelta * capheight end
    local ascent    = parameters.ascent    if ascent    then targetparameters.ascent    = vdelta * ascent    end
    local descent   = parameters.descent   if descent   then targetparameters.descent   = vdelta * descent   end
    --
    constructors.enhanceparameters(targetparameters) -- official copies for us, now virtual
    --
    local scaledwidth  = defaultwidth  * hdelta
    local scaledheight = defaultheight * vdelta
    local scaleddepth  = defaultdepth  * vdelta
    --
    local textcontrol = properties.textcontrol or 0
    if targetproperties.mode == "base" then
        textcontrol = textcontrol | base_ligaturing | base_kerning
        --
        -- the only drawback is when we add some butthat doesn't happen in base mode so
        -- we need to test this as there can be a little gain
        --
     -- if haskerns then
     --     textcontrol = textcontrol | base_kerning
     -- end
     -- if hasligatures then
     --     textcontrol = textcontrol | base_ligaturing
     -- end
    elseif targetproperties.mode == "none" then
        textcontrol = textcontrol | none_protected -- or just assign
    end
    if hasitalics then
        textcontrol = textcontrol | has_italics
    end
    if useditalicangle ~= 0 or usedslant ~= 0 then
        textcontrol = textcontrol | auto_italics
    end
    targetproperties.textcontrol = textcontrol
    target.textcontrol = textcontrol
    --
    local hasmath = (properties.hasmath or next(mathparameters)) and true
    --
    if hasmath then
        constructors.assignmathparameters(target,tfmdata) -- does scaling and whatever is needed

        properties.hasmath   = true  -- to be sure
        target.nomath        = false -- hm
        target.MathConstants = target.mathparameters

        local compactmath    = properties.compactmath

        targetproperties.compactmath = compactmath
        target.compactmath           = compactmath

        local textscale         = parameters.textscale         -- 1000
        local scriptscale       = parameters.scriptscale       --  700
        local scriptscriptscale = parameters.scriptscriptscale --  500

        parameters.textscale         = textscale
        parameters.scriptscale       = scriptscale
        parameters.scriptscriptscale = scriptscriptscale

        target.textscale         = textscale
        target.scriptscale       = scriptscale
        target.scriptscriptscale = scriptscriptscale

        targetparameters.textscale         = textscale
        targetparameters.scriptscale       = scriptscale
        targetparameters.scriptscriptscale = scriptscriptscale

--         local scriptxscale       = parameters.scriptxscale
--         local scriptyscale       = parameters.scriptyscale
--         local scriptscriptxscale = parameters.scriptscriptxscale
--         local scriptscriptyscale = parameters.scriptscriptyscale

--         targetparameters.scriptxscale       = scriptxscale
--         targetparameters.scriptyscale       = scriptyscale
--         targetparameters.scriptscriptxscale = scriptscriptxscale
--         targetparameters.scriptscriptyscale = scriptscriptyscale

--         target.scriptxscale       = scriptxscale
--         target.scriptyscale       = scriptyscale
--         target.scriptscriptxscale = scriptscriptxscale
--         target.scriptscriptyscale = scriptscriptyscale

        local mathcontrol = properties.mathcontrol
        targetproperties.mathcontrol = mathcontrol
        target.mathcontrol = mathcontrol
    else
        properties.hasmath    = false
        target.nomath         = true
        target.mathparameters = nil -- nop
    end
    --
    if trace_defining then
        report_defining("defining tfm, name %a, fullname %a, filename %a, %spsname %a, hscale %a, vscale %a, math %a, italics %a",
            name,fullname,filename,psfixed and "(fixed) " or "",psname,hdelta,vdelta,
            hasmath and "enabled" or "disabled",hasitalics and "enabled" or "disabled")
    end
    --
    constructors.beforecopyingcharacters(target,tfmdata)
    --
    local sharedkerns = { }
    --
    -- we can have a dumb mode (basemode without math etc) that skips most
    --
    for unicode, character in next, characters do
        local chr, description, index
        -- todo: add description entry to char entry instead of index ... saves elsewhere too
        if changed then
            local c = changed[unicode]
            if c and c ~= unicode then
                local cc = changed[c]
                if cc then
                    while cc and cc ~= unicode and c ~= cc do -- bidi symbol swap catch
                        c = cc
                        cc = changed[c]
                    end
                end
                -- check not needed:
                if c then
                    description = descriptions[c] or descriptions[unicode] or character
                    character   = characters[c] or character
                    index       = description.index or c
                else
                    description = descriptions[unicode] or character
                    index       = description.index or unicode
                end
            else
                description = descriptions[unicode] or character
                index       = description.index or unicode
            end
        else
            description = descriptions[unicode] or character
            index       = description.index or unicode
        end
        local width     = description.width
        local height    = description.height
        local depth     = description.depth
        local isunicode = description.unicode
        if width  then width  = hdelta*width  else width  = scaledwidth  end
        if height then height = vdelta*height else height = scaledheight end
    --  if depth  then depth  = vdelta*depth  else depth  = scaleddepth  end
        if depth and depth ~= 0 then
            depth = vdelta*depth
            if isunicode then
                chr = {
                    index   = index,
                    height  = height,
                    depth   = depth,
                    width   = width,
                    unicode = isunicode,
                }
            else
                chr = {
                    index  = index,
                    height = height,
                    depth  = depth,
                    width  = width,
                }
            end
        else
            if isunicode then
                chr = {
                    index   = index,
                    height  = height,
                    width   = width,
                    unicode = isunicode,
                }
            else
                chr = {
                    index  = index,
                    height = height,
                    width  = width,
                }
            end
        end
        local bb = description.boundingbox
        if bb then
            if bb[2] > 0 then
                chr.tag = abovebaseline_tag
            elseif bb[4] < 0 then
                chr.tag = belowbaseline_tag
            end
        end
        if hasquality then
            -- we could move these calculations elsewhere (saves calculations)
            local ve = character.expansion
            if ve then
                chr.expansion = ve*1000 -- expansionfactor, hm, can happen elsewhere
            end
            local vc = character.compression
            if vc then
                chr.compression = vc*1000 -- expansionfactor, hm, can happen elsewhere
            end
            local vl = character.leftprotrusion
            if vl then
                chr.leftprotrusion = width*vl
            end
            local vr = character.rightprotrusion
            if vr then
                chr.rightprotrusion = width*vr
            end
        end
        --
        if hasmath then
            local nxt = character.next
            if nxt then
                chr.next = nxt
            end
            local parts = character.parts
            if parts then
                local orientation = character.partsorientation or "vertical"
                chr.parts = scaleparts(parts,orientation == "horizontal" and hdelta or vdelta)
                chr.partsorientation = orientation
            end
            local vi = character.partsitalic
            if vi and vi ~= 0 then
                chr.partsitalic = vi*hdelta
            end
            local va = character.topanchor
            if va and va ~= 0 then
             -- chr.topanchor = va*vdelta
                chr.topanchor = va*hdelta
            end
            va = character.bottomanchor
            if va and va ~= 0 then
             -- chr.bottomanchor = va*vdelta
                chr.bottomanchor = va*hdelta
            end
            --
            local mk = character.mathkerns
            if mk then
                local tr = mk.topright
                local tl = mk.topleft
                local br = mk.bottomright
                local bl = mk.bottomleft
                chr.mathkerns = {
                    topright    = tr and mathkerns(tr,vdelta) or nil,
                    topleft     = tl and mathkerns(tl,vdelta) or nil,
                    bottomright = br and mathkerns(br,vdelta) or nil,
                    bottomleft  = bl and mathkerns(bl,vdelta) or nil,
                }
            end
            --
            if hasitalics then
                local vi = character.italic
                if vi and vi ~= 0 then
                    chr.italic = vi*hdelta
                end
            end
            --
            -- These can never happen here as these come from tweaks but I need to check it:
            --
         -- local vo = character.topovershoot
         -- if vo and vo ~= 0 then
         --     chr.topovershoot = vo*hdelta
         -- end
         -- local il = character.innerlocation
         -- if il then
         --     chr.innerlocation = il
         --     chr.innerxoffset  = (character.innerxoffset or 0) * hdelta
         --     chr.inneryoffset  = (character.inneryoffset or 0) * vdelta
         -- end
            --
         -- if character.extensible then
         --     chr.extensible = true -- stretch fixed width accent
         -- end
         -- --
         -- local k = character.topleft      if k and k ~= 0 then chr.topleft      = k*hdelta end
         -- local k = character.topright     if k and k ~= 0 then chr.topright     = k*hdelta end
         -- local k = character.bottomleft   if k and k ~= 0 then chr.bottomleft   = k*hdelta end
         -- local k = character.bottomright  if k and k ~= 0 then chr.bottomright  = k*hdelta end
         -- --
         -- local m = character.leftmargin   if m and m ~= 0 then chr.leftmargin   = m*hdelta end
         -- local m = character.rightmargin  if m and m ~= 0 then chr.rightmargin  = m*hdelta end
         -- local m = character.topmargin    if m and m ~= 0 then chr.topmargin    = m*hdelta end
         -- local m = character.bottommargin if m and m ~= 0 then chr.bottommargin = m*hdelta end
            --
            local sm = character.smaller
            if sm then
                chr.smaller = sm
            end
         -- local mi = character.mirror
         -- if mi then
         --     chr.mirror = mi
         -- end
            local fa = character.flataccent -- set here?
            if fa then
                chr.flataccent = fa
            end
        elseif hasitalics then -- unlikely
            local vi = character.italic
            if vi and vi ~= 0 then
                chr.italic = vi*hdelta
            end
        end
        if haskerns then
            local vk = character.kerns
            if vk then
                local s = sharedkerns[vk]
                if not s then
                    s = { }
                    for k,v in next, vk do s[k] = v*hdelta end
                    sharedkerns[vk] = s
                end
                chr.kerns = s
            end
        end
        if hasligatures then
            local vl = character.ligatures
            if vl then
                if true then
                    chr.ligatures = vl -- shared
                else
                    local tt = { }
                    for i, l in next, vl do
                        tt[i] = l
                    end
                    chr.ligatures = tt
                end
            end
        end
        -- only in old school fonts (otherwise we set commands later in context)
        local vc = character.commands
        if vc then
            chr.commands = scalecommands(vc,hdelta,vdelta)
        end
        local cb = character.callback
        if cb then
            chr.callback = cb
        end
        -- we assume that these are done in a manipulator afterwards
        -- as it makes no sense otherwise
     -- local vx = character.xoffset
     -- if vx then
     --     chr.xoffset = vx*hdelta
     -- end
     -- local vy = character.yoffset
     -- if vy then
     --     chr.yoffset = vy*vdelta
     -- end
     -- local va = character.advance
     -- if va then
     --     chr.advance = va*vdelta
     -- end
        targetcharacters[unicode] = chr
    end
    --
    properties.setitalics = hasitalics -- for postprocessing
    --
 -- if hasmath then properties.mode = "none" end -- possible optimization but it needs testing
    --
    constructors.aftercopyingcharacters(target,tfmdata)
    --
    constructors.beforepassingfonttotex(target,tfmdata) -- mayeb move into aftercopyingcharacters
    --
    constructors.trytosharefont(target,tfmdata)
    --
    -- catch inconsistencies (for now)
    --
    local vfonts = target.fonts
    if not vfonts or #vfonts == 0 then
        target.fonts = { { id = 0 } }
    end
    --
    if trace_defining then
        report_defining("font %a, size %N, delta %N",name,scaledpoints,delta)
    end
    --
    return target
end

function constructors.finalize(tfmdata)
    if tfmdata.properties and tfmdata.properties.finalized then
        return
    end
    --
    if not tfmdata.characters then
        return nil
    end
    --
    if not tfmdata.goodies then
        tfmdata.goodies = { }
    end
    --
    local parameters = tfmdata.parameters
    local properties = tfmdata.properties
    if not parameters then
        return nil
    end
    --
 -- if not parameters.expansion then
 --     parameters.expansion = {
 --         stretch = tfmdata.stretch or 0,
 --         shrink  = tfmdata.shrink  or 0,
 --         step    = tfmdata.step    or 0,
 --     }
 -- end
    --
    if not parameters.size then
        parameters.size = tfmdata.size
    end
    --
    -- In the backend we need to know what effects have been applied. The font
    -- has been scaled contrary to the effects that we apply per glyph as these
    -- are delayed. We can actually also use the font specific effects there.
    --
    local effect = properties and properties.effect
    if effect then
        parameters.mode          = effect.mode
        parameters.width         = effect.width
        parameters.slantfactor   = effect.slant
        parameters.extendfactor  = effect.extend
        parameters.squeezefactor = effect.squeeze
    else
        parameters.mode          = 0
        parameters.width         = 0
        parameters.slantfactor   = 0
        parameters.extendfactor  = 1
        parameters.squeezefactor = 1
    end
    local designsize = parameters.designsize
    if designsize then
        parameters.minsize = tfmdata.minsize or designsize
        parameters.maxsize = tfmdata.maxsize or designsize
    else
        designsize = factors.pt * 10
        parameters.designsize = designsize
        parameters.minsize    = designsize
        parameters.maxsize    = designsize
    end
    parameters.minsize = tfmdata.minsize or parameters.designsize
    parameters.maxsize = tfmdata.maxsize or parameters.designsize
    --
    if not parameters.units then
        parameters.units = tfmdata.units or 1000
    end
    --
    if not tfmdata.descriptions then
        local descriptions = { } -- yes or no
        setmetatableindex(descriptions, function(t,k) local v = { } t[k] = v return v end)
        tfmdata.descriptions = descriptions
    end
    --
    local properties = tfmdata.properties
    if not properties then
        properties = { }
        tfmdata.properties = properties
    end
    --
    properties.fontname      = properties.fontname or tfmdata.fontname
    properties.filename      = properties.filename or tfmdata.filename
    properties.fullname      = properties.fullname or tfmdata.fullname
    properties.name          = properties.name     or tfmdata.name
    properties.psname        = properties.psname   or tfmdata.psname
    --
    properties.subfont       = tfmdata.subfont       or nil
    properties.cidinfo       = tfmdata.cidinfo       or nil
    properties.format        = tfmdata.format        or "type1"
    properties.writingmode   = tfmdata.writingmode   or "horizontal"
    properties.usedbitmap    = tfmdata.usedbitmap
    --
    if not tfmdata.resources then
        tfmdata.resources = { }
    end
    if not tfmdata.shared then
        tfmdata.shared = { }
    end
    --
    -- tfmdata.fonts
    -- tfmdata.unscaled
    --
    if not properties.hasmath then
        properties.hasmath = not tfmdata.nomath
    end
    --
    tfmdata.MathConstants    = nil
    tfmdata.postprocessors   = nil
    --
    tfmdata.fontname         = nil
    tfmdata.filename         = nil
    tfmdata.fullname         = nil
    tfmdata.name             = nil -- most tricky part
    tfmdata.psname           = nil
    --
    tfmdata.subfont          = nil
    tfmdata.cidinfo          = nil
    tfmdata.format           = nil
    tfmdata.nomath           = nil
    tfmdata.designsize       = nil
    --
    tfmdata.size             = nil
 -- tfmdata.stretch          = nil
 -- tfmdata.shrink           = nil
 -- tfmdata.step             = nil
    tfmdata.slant            = nil
    tfmdata.extend           = nil
    tfmdata.squeeze          = nil
    tfmdata.mode             = nil
    tfmdata.width            = nil
    tfmdata.units            = nil
    --
    tfmdata.cache            = nil
    --
    properties.finalized     = true
    --
    return tfmdata
end

-- A unique hash value is generated by:

local hashmethods        = { }
constructors.hashmethods = hashmethods

function constructors.hashfeatures(specification) -- will be overloaded
    local features = specification.features
    if features then
        local t, n = { }, 0
        for category, list in sortedhash(features) do
            if next(list) then
                local hasher = hashmethods[category]
                if hasher then
                    local hash = hasher(list)
                    if hash then
                        n = n + 1
                        t[n] = category .. ":" .. hash
                    end
                end
            end
        end
        if n > 0 then
            return concat(t," & ")
        end
    end
    return "unknown"
end

hashmethods.normal = function(list)
    local s = { }
    local n = 0
    for k, v in next, list do
        if not k then
            -- no need to add to hash
        elseif k == "number" or k == "features" then
            -- no need to add to hash (maybe we need a skip list)
        else
            n = n + 1
            if type(v) == "table" then
                -- table.sequenced
                local t = { }
                local m = 0
                for k, v in next, v do
                    m = m + 1
                    t[m] = k .. '=' .. tostring(v)
                end
                sort(t)
                s[n] = k .. '={' .. concat(t,",") .. "}"
            else
                s[n] = k .. '=' .. tostring(v)
            end
        end
    end
    if n > 0 then
        sort(s)
        return concat(s,"+")
    end
end

-- In principle we can share tfm tables when we are in need for a font, but then we
-- need to define a font switch as an id/attr switch which is no fun, so in that
-- case users can best use dynamic features ... so, we will not use that speedup.
-- Okay, when we get rid of base mode we can optimize even further by sharing, but
-- then we loose our testcases for LuaTeX.

function constructors.hashinstance(specification,force)
    -- implemented in font-ctx.lmt
end

function constructors.setname(tfmdata,specification)
    -- dummy (still called in generic font-otl)
end

function constructors.checkedfilename(data)
    local foundfilename = data.foundfilename
    if not foundfilename then
        local askedfilename = data.filename or ""
        if askedfilename ~= "" then
            askedfilename = resolvers.resolve(askedfilename) -- no shortcut
            foundfilename = resolvers.findbinfile(askedfilename,"") or ""
            if foundfilename == "" then
                report_defining("source file %a is not found",askedfilename)
                foundfilename = resolvers.findbinfile(file.basename(askedfilename),"") or ""
                if foundfilename ~= "" then
                    report_defining("using source file %a due to cache mismatch",foundfilename)
                end
            end
        end
        data.foundfilename = foundfilename
    end
    return foundfilename
end

local formats = allocate()
fonts.formats = formats

setmetatableindex(formats, function(t,k)
    local l = lower(k)
    if rawget(t,k) then
        t[k] = l
        return l
    end
    return rawget(t,file.suffix(l))
end)

do

    local function setindeed(mode,source,target,group,name,position)
        local action = source[mode]
        if not action then
            return
        end
        local t = target[mode]
        if not t then
            report_defining("fatal error in setting feature %a, group %a, mode %a",name,group,mode)
            os.exit()
        elseif position then
            -- todo: remove existing
            insert(t, position, { name = name, action = action })
        else
            for i=1,#t do
                local ti = t[i]
                if ti.name == name then
                    ti.action = action
                    return
                end
            end
            insert(t, { name = name, action = action })
        end
    end

    local function set(group,name,target,source)
        target = target[group]
        if not target then
            report_defining("fatal target error in setting feature %a, group %a",name,group)
            os.exit()
        end
        local source = source[group]
        if not source then
            report_defining("fatal source error in setting feature %a, group %a",name,group)
            os.exit()
        end
        local position = source.position
        setindeed("node",source,target,group,name,position)
        setindeed("base",source,target,group,name,position)
        setindeed("none",source,target,group,name,position)
        setindeed("plug",source,target,group,name,position)
    end

    local function register(where,specification)
        local name = specification.name
        if name and name ~= "" then
            local default      = specification.default
            local description  = specification.description
            local initializers = specification.initializers
            local processors   = specification.processors
            local manipulators = specification.manipulators
            local modechecker  = specification.modechecker
            if default then
                where.defaults[name] = default
            end
            if description and description ~= "" then
                where.descriptions[name] = description
            end
            if initializers then
                set('initializers',name,where,specification)
            end
            if processors then
                set('processors',  name,where,specification)
            end
            if manipulators then
                set('manipulators',name,where,specification)
            end
            if modechecker then
               where.modechecker = modechecker
            end
        end
    end

    constructors.registerfeature = register

    function constructors.getfeatureaction(what,where,mode,name)
        what = handlers[what].features
        if what then
            where = what[where]
            if where then
                mode = where[mode]
                if mode then
                    for i=1,#mode do
                        local m = mode[i]
                        if m.name == name then
                            return m.action
                        end
                    end
                end
            end
        end
    end

    local newfeatures        = { }
    constructors.newfeatures = newfeatures -- downward compatible
    constructors.features    = newfeatures

    local function setnewfeatures(what)
        local handler  = handlers[what]
        local features = handler.features
        if not features then
            local tables     = handler.tables     -- can be preloaded
            local statistics = handler.statistics -- can be preloaded
            features = allocate {
                defaults     = { },
                descriptions = tables and tables.features or { },
                used         = statistics and statistics.usedfeatures or { },
                initializers = { base = { }, node = { }, none = { }, plug = { } },
                processors   = { base = { }, node = { }, none = { }, plug = { } },
                manipulators = { base = { }, node = { }, none = { }, plug = { } },
                finalizers   = { base = { }, node = { }, none = { }, plug = { } },
            }
            features.register = function(specification) return register(features,specification) end
            handler.features = features -- will also become hidden
        end
        return features
    end

    setmetatable(newfeatures, {
        __call  = function(t,k) local v = t[k] return v end,
        __index = function(t,k) local v = setnewfeatures(k) t[k] = v return v end,
    })

end

do

    local newhandler        = { }
    constructors.handlers   = newhandler -- downward compatible
    constructors.newhandler = newhandler

    local function setnewhandler(what) -- could be a metatable newindex
        local handler = handlers[what]
        if not handler then
            handler = { }
            handlers[what] = handler
        end
        return handler
    end

    setmetatable(newhandler, {
        __call  = function(t,k) local v = t[k] return v end,
        __index = function(t,k) local v = setnewhandler(k) t[k] = v return v end,
    })

end

do
    -- a pitty that we need to be generic as we have nicer mechanisms for this ...

    local newenhancer        = { }
    constructors.enhancers   = newenhancer
    constructors.newenhancer = newenhancer

    local function setnewenhancer(format)

        local handler   = handlers[format]
        local enhancers = handler.enhancers

        if not enhancers then

            local actions  = allocate() -- no need to allocate thee
            local before   = allocate()
            local after    = allocate()
            local order    = allocate()
            local known    = { }
            local nofsteps = 0
            local patches  = { before = before, after = after }

            local trace   = false
            local report  = logs.reporter("fonts",format .. " enhancing")

            trackers.register(format .. ".loading", function(v) trace = v end)

            local function enhance(name,data,filename,raw)
                local enhancer = actions[name]
                if enhancer then
                    if trace then
                        report("apply enhancement %a to file %a",name,filename)
                        ioflush()
                    end
                    enhancer(data,filename,raw)
                else
                    -- no message as we can have private ones
                end
            end

            local function apply(data,filename,raw)
                local basename = file.basename(lower(filename))
                if trace then
                    report("%s enhancing file %a","start",filename)
                end
                ioflush() -- we want instant messages
                for e=1,nofsteps do
                    local enhancer = order[e]
                    local b = before[enhancer]
                    if b then
                        for pattern, action in next, b do
                            if find(basename,pattern) then
                                action(data,filename,raw)
                            end
                        end
                    end
                    enhance(enhancer,data,filename,raw) -- we have one installed: check extra features
                    local a = after[enhancer]
                    if a then
                        for pattern, action in next, a do
                            if find(basename,pattern) then
                                action(data,filename,raw)
                            end
                        end
                    end
                    ioflush() -- we want instant messages
                end
                if trace then
                    report("%s enhancing file %a","stop",filename)
                end
                ioflush() -- we want instant messages
            end

            local function register(what,action)
                if action then
                    if actions[what] then
                        -- overloading, e.g."check extra features"
                    else
                        nofsteps        = nofsteps + 1
                        order[nofsteps] = what
                        known[what]     = nofsteps
                    end
                    actions[what] = action
                else
                    report("bad enhancer %a",what)
                end
            end

            -- We used to have a lot of enhancers but no longer with the new font loader. The order of enhancers
            -- is the order of definition. The before/after patches are there for old times sake and happen
            -- before or after a (named) enhancer. An example of a set enhancer is "check extra features" so one
            -- one set patches before or after that is applied. Unknown enhancers are auto-registered. It's a bit
            -- messy but we keep it for compatibility reasons.
            --
            -- fonts.handlers.otf.enhancers.patches.register("before","some patches","somefont",function(data,filename)
            --     print("!!!!!!!") -- before | after
            -- end)
            --
            -- fonts.handlers.otf.enhancers.register("more patches",function(data,filename)
            --     print("???????") -- enhance
            -- end)

            local function patch(what,where,pattern,action)
                local pw = patches[what]
                if pw then
                    local ww = pw[where]
                    if ww then
                        ww[pattern] = action
                    else
                        pw[where] = { [pattern] = action }
                        if not known[where] then
                            nofsteps        = nofsteps + 1
                            order[nofsteps] = where
                            known[where]    = nofsteps
                        end
                    end
                end
            end

            enhancers = {
                register = register,
                apply    = apply,
                patch    = patch,
                report   = report,
                patches  = {
                    register = patch,
                    report   = report,
                }, -- for old times sake
            }

            handler.enhancers = enhancers
        end
        return enhancers
    end

    setmetatable(newenhancer, {
        __call  = function(t,k) local v = t[k] return v end,
        __index = function(t,k) local v = setnewenhancer(k) t[k] = v return v end,
    })

end

-- We need to check for default features. For this we provide a helper function.

function constructors.checkedfeatures(what,features)
    local defaults = handlers[what].features.defaults
    if features and next(features) then
        features = fastcopy(features) -- can be inherited (mt) but then no loops possible
        for key, value in next, defaults do
            if features[key] == nil then
                features[key] = value
            end
        end
        return features
    else
        return fastcopy(defaults) -- we can change features in place
    end
end

-- before scaling

function constructors.initializefeatures(what,tfmdata,features,trace,report)
    if features and next(features) then
        local properties       = tfmdata.properties or { } -- brrr
        local whathandler      = handlers[what]
        local whatfeatures     = whathandler.features
        local whatmodechecker  = whatfeatures.modechecker
        -- properties.mode can be enforces (for instance in font-otd)
        local mode             = properties.mode or (whatmodechecker and whatmodechecker(tfmdata,features,features.mode)) or features.mode or "base"
        properties.mode        = mode -- also status
        features.mode          = mode -- both properties.mode or features.mode can be changed
        --
        local done             = { }
        while true do
            local redo = false
            local initializers = whatfeatures.initializers[mode]
            if initializers then
                for i=1,#initializers do
                    local step = initializers[i]
                    local feature = step.name
-- we could intercept mode here .. needs a rewrite of this whole loop then but it's cleaner that way
                    local value = features[feature]
                    if not value then
                        -- disabled
                    elseif done[feature] then
                        -- already done
                    else
                        local action = step.action
                        if trace then
                            report("initializing feature %a to %a for mode %a for font %a",feature,
                                value,mode,tfmdata.properties.fullname)
                        end
                        action(tfmdata,value,features) -- can set mode (e.g. goodies) so it can trigger a restart
                        if mode ~= properties.mode or mode ~= features.mode then
                            if whatmodechecker then
                                properties.mode = whatmodechecker(tfmdata,features,properties.mode) -- force checking
                                features.mode   = properties.mode
                            end
                            if mode ~= properties.mode then
                                mode = properties.mode
                                redo = true
                            end
                        end
                        done[feature] = true
                    end
                    if redo then
                        break
                    end
                end
                if not redo then
                    break
                end
            else
                break
            end
        end
        properties.mode = mode -- to be sure
        return true
    else
        return false
    end
end

-- while typesetting

function constructors.collectprocessors(what,tfmdata,features,trace,report)
    local processes    = { }
    local nofprocesses = 0
    if features and next(features) then
        local properties     = tfmdata.properties
        local whathandler    = handlers[what]
        local whatfeatures   = whathandler.features
        local whatprocessors = whatfeatures.processors
        local mode           = properties.mode
        local processors     = whatprocessors[mode]
        if processors then
            for i=1,#processors do
                local step = processors[i]
                local feature = step.name
                if features[feature] then
                    local action = step.action
                    if trace then
                        report("installing feature processor %a for mode %a for font %a",feature,mode,tfmdata.properties.fullname)
                    end
                    if action then
                        nofprocesses = nofprocesses + 1
                        processes[nofprocesses] = action
                    end
                end
            end
        elseif trace then
            report("no feature processors for mode %a for font %a",mode,properties.fullname)
        end
    end
    return processes
end

-- after scaling

local function apply(key,what,tfmdata,features,trace,report)
    if features and next(features) then
        local properties   = tfmdata.properties
        local whathandler  = handlers[what]
        local whatfeatures = whathandler.features
        local whatactions  = whatfeatures[key]
        local mode         = properties.mode
        local actions      = whatactions[mode]
        if actions then
            for i=1,#actions do
                local step    = actions[i]
                local feature = step.name
                local value   = features[feature]
                if value then
                    local action = step.action
                    if trace then
                        report("applying feature %s %a for mode %a for font %a",key,feature,mode,properties.fullname)
                    end
                    if action then
                        action(tfmdata,feature,value)
                    end
                end
            end
        end
    end
end

function constructors.applymanipulators(what,tfmdata,features,trace,report)
    if features and next(features) then
        apply("manipulators",what,tfmdata,features,trace,report)
    end
end

function constructors.applyfinalizers(what,tfmdata,features,trace,report)
    if features and next(features) then
        apply("finalizers",what,tfmdata,features,trace,report)
    end
end

function constructors.addcoreunicodes(unicodes) -- maybe make this a metatable if used at all
    if not unicodes then
        unicodes = { }
    end
    unicodes.space  = 0x0020
    unicodes.hyphen = 0x002D
    unicodes.zwj    = 0x200D
    unicodes.zwnj   = 0x200C
    return unicodes
end
