if not modules then modules = { } end modules ['font-ogr'] = {
    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"
}

-- Here we deal with graphic variants and for now also color support ends up here
-- but that might change. It's lmtx only code.

local tostring, tonumber, next, type, rawget = tostring, tonumber, next, type, rawget
local round, max, mod, div = math.round, math.max, math.mod, math.div
local find = string.find
local concat, setmetatableindex, sortedhash = table.concat, table.setmetatableindex, table.sortedhash
local utfbyte = utf.byte
local formatters = string.formatters
local settings_to_hash_strict, settings_to_array = utilities.parsers.settings_to_hash_strict, utilities.parsers.settings_to_array

local otf         = fonts.handlers.otf
local otfregister = otf.features.register
otf.svgenabled    = true
otf.pngenabled    = true

-- Just to remind me ... rewritten around the time this was posted on YT which
-- was also around the 2019 ctx meeting:
--
-- Gavin Harrison - "Threatening War" by The Pineapple Thief
-- https://www.youtube.com/watch?v=ENF9wth4kwM

-- todo: svg color plugin
-- todo: get rid of double cm in svg (tricky as also elsewhere)
-- todo: png offsets (depth)
-- todo: maybe collapse indices so that we have less files (harder to debug)
-- todo: manage (read: assign) font id's in lua so we know in advance

-- what here and what in backend ...

local report_fonts = logs.reporter("backend","fonts")
local trace_fonts    trackers.register("backend.fonts",function(v) trace_fonts = v end)

local slotcommand = fonts.helpers.commands.slot

do

    -- This is a prelude to something better but I'm still experimenting. We should delay more.

    local dropins     = { }
    fonts.dropins     = dropins
    local droppedin   = 0
    local identifiers = fonts.hashes.identifiers

    function dropins.nextid()
        droppedin = droppedin - 1
        return droppedin
    end

    -- todo: pass specification table instead

    function dropins.provide(method,t_tfmdata,indexdata,...)
        local droppedin           = dropins.nextid()
        local t_characters        = t_tfmdata.characters
        local t_descriptions      = t_tfmdata.descriptions
        local t_properties        = t_tfmdata.properties
        local d_tfmdata           = setmetatableindex({ },t_tfmdata)
        local d_properties        = setmetatableindex({ },t_properties)
        local d_basefontname      = "ContextRuntimeFont" .. droppedin
        d_properties.basefontname = d_basefontname
        d_tfmdata.properties      = d_properties
        local d_characters        = { } -- setmetatableindex({ },t_characters)   -- hm, index vs unicode
        local d_descriptions      = { } -- setmetatableindex({ },t_descriptions) -- hm, index vs unicode
        d_tfmdata.characters      = d_characters
        d_tfmdata.descriptions    = d_descriptions
        d_tfmdata.parentdata      = t_tfmdata -- so we can access it if needed
        d_properties.instance     = - droppedin -- will become an extra element in the hash
        identifiers[droppedin]    = d_tfmdata
        local fonts               = t_tfmdata.fonts or { }
        t_tfmdata.fonts           = fonts
        d_properties.format       = "type3"
        d_properties.method       = method
        d_properties.indexdata    = { indexdata, ... } -- can take quite some memory
        local slot                = #fonts + 1
        fonts[slot]               = { id = droppedin }
        if trace_fonts then
            report_fonts("registering dropin %a using method %a",d_basefontname,method)
        end
        return slot, droppedin, d_tfmdata, d_properties
    end

    -- todo: delay this, in which case we can be leaner and meaner

    function dropins.clone(method,tfmdata,shapes,...) -- by index
        if method and shapes then
            local characters   = tfmdata.characters
            local descriptions = tfmdata.descriptions
            local droppedin, tfmdrop, dropchars, dropdescs, colrshapes, props
            local idx  = 255
            local slot = 0
            -- sorted ?
            for k, v in next, characters do
                local index = v.index
                if index then
                    local description = descriptions[k]
                    if description then
                        local shape = shapes[index]
                        if shape then
                            if idx >= 255 then
                                idx = 1
                                colrshapes = setmetatableindex({ },shapes)
                                slot, droppedin, tfmdrop, props = dropins.provide(method,tfmdata,colrshapes)
                                dropchars = tfmdrop.characters
                                dropdescs = tfmdrop.descriptions
                            else
                                idx = idx + 1
                            end
                            colrshapes[idx] = shape -- so not: description
                            -- todo: prepend
                            v.commands = { slotcommand[slot][idx] }
                            -- hack to prevent that type 3 also gets 'use' flags .. todo
                            local c = { commands = false, index = idx, dropin = tfmdrop }
                            local d = { index = idx, dropin = tfmdrop }
                            setmetatableindex(c,v)
                            setmetatableindex(d,description)
                            dropchars[idx] = c
                            dropdescs[idx] = d -- not needed
                        end
                    end
                end
            end
        else
            -- error
        end
    end

    function dropins.swap(method,tfmdata,shapes) -- by unicode
        if method and shapes then
            local characters   = tfmdata.characters
            local descriptions = tfmdata.descriptions
            local droppedin    = tfmdata.droppedin
            local tfmdrop      = tfmdata.tfmdrop
            local dropchars    = tfmdata.dropchars
         -- local dropdescs    = tfmdata.dropdescs
            local colrshapes   = tfmdata.colrshapes
            local idx          = tfmdata.dropindex or 255
            local slot         = tfmdata.dropslot  or 0
            -- we can have a variant where shaped are by unicode and not by index
            for k, v in next, characters do
                local description = descriptions[k]
                if description then
                    local shape = shapes[k]
                    if shape then
                        if idx >= 255 then
                            idx = 1
                            colrshapes = setmetatableindex({ },shapes)
                            slot, droppedin, tfmdrop = dropins.provide(method,tfmdata,colrshapes)
                            dropchars = tfmdrop.characters
                         -- dropdescs = tfmdrop.descriptions
                        else
                            idx = idx + 1
                        end
                        colrshapes[idx] = shape -- so not: description
                        -- todo: prepend
                        v.commands = { slotcommand[slot][idx] }
                        -- hack to prevent that type 3 also gets 'use' flags .. todo
                        local c = { commands = false, index = idx, dropin = tfmdrop }
                        local d = { } -- index = idx, dropin = tfmdrop }
                        setmetatableindex(c,v)
                        setmetatableindex(d,description)
                        dropchars[idx] = c
                     -- dropdescs[idx] = d -- not needed
                    end
                end
            end
            tfmdata.droppedin  = droppedin
            tfmdata.tfmdrop    = tfmdrop
            tfmdata.dropchars  = dropchars
         -- tfmdata.dropdescs  = dropdescs
            tfmdata.colrshapes = colrshapes
            tfmdata.dropindex  = idx
            tfmdata.dropslot   = slot
        else
            -- error
        end
    end

    function dropins.swapone(method,tfmdata,shape,unicode)
        if method and shape then
            local characters   = tfmdata.characters
            local descriptions = tfmdata.descriptions
            local droppedin    = tfmdata.droppedin
            local tfmdrop      = tfmdata.tfmdrop
            local dropchars    = tfmdata.dropchars
        --  local dropdescs    = tfmdata.dropdescs
            local colrshapes   = tfmdata.colrshapes
            local idx          = tfmdata.dropindex or 255
            local slot         = tfmdata.dropslot  or 0
            local character    = characters[unicode]
        --  local description  = descriptions[unicode] or { }
            if character then
                if idx >= 255 then
                    idx = 1
                    colrshapes = setmetatableindex({ },shapes)
                    slot, droppedin, tfmdrop = dropins.provide(method,tfmdata,colrshapes)
                    dropchars = tfmdrop.characters
                 -- dropdescs = tfmdrop.descriptions
                else
                    idx = idx + 1
                end
                colrshapes[idx] = shape.code -- so not: description
                -- todo: prepend
                character.commands = { slotcommand[slot][idx] }
                -- hack to prevent that type 3 also gets 'use' flags .. todo
                local c = { commands = false, index = idx, dropin = tfmdrop }
             -- local d = { } -- index = idx, dropin = tfmdrop }
                setmetatableindex(c,character)
             -- setmetatableindex(d,description)
                dropchars[idx] = c
             -- dropdescs[idx] = d -- not needed
            end
            tfmdata.droppedin  = droppedin
            tfmdata.tfmdrop    = tfmdrop
            tfmdata.dropchars  = dropchars
         -- tfmdata.dropdescs  = dropdescs
            tfmdata.colrshapes = colrshapes
            tfmdata.dropindex  = idx
            tfmdata.dropslot   = slot
        end
    end

end

do -- this will move to its own module

    local dropins = fonts.dropins

    local shapes = setmetatableindex(function(t,k)
        local v = {
            glyphs     = { },
            parameters = {
                units = 10
            },
        }
        t[k] = v
        return v
    end)

    function dropins.getshape(name,n)
        local s = shapes[name]
        return s and s.glyphs and s.glyphs[n]
    end

    function dropins.getshapes(name)
        return shapes[name]
    end

    function dropins.registerglyphs(parameters)
        local category = parameters.name
        local target   = shapes[category].parameters
        for k, v in next, parameters do
            if k ~= "glyphs" then
                target[k] = v
            end
        end
    end

    function dropins.registerglyph(parameters)
        local category = parameters.category
        local unicode  = parameters.unicode
        local private  = parameters.private
        local unichar  = parameters.unichar
        if private then
            unicode = fonts.helpers.newprivateslot(private)
        elseif type(unichar) == "string" then
            unicode = utfbyte(unichar)
        else
            local unitype = type(unicode)
            if unitype == "string" then
                local uninumber = tonumber(unicode)
                if uninumber then
                    unicode = round(uninumber)
                else
                    unicode = utfbyte(unicode)
                end
            elseif unitype == "number" then
                unicode = round(unicode)
            end
        end
        if unicode then
            parameters.unicode = unicode
         -- print(category,unicode)
            shapes[category].glyphs[unicode] = parameters
        else
            -- error
        end
    end

 -- local function hascolorspec(t)
 --     if (t.color or "") ~= "" then
 --         return true
 --     elseif (t.fillcolor or "") ~= "" then
 --         return true
 --     elseif (t.drawcolor or "") ~= "" then
 --         return true
 --     elseif (t.linecolor or "") ~= "" then
 --         return true
 --     else
 --         return false
 --     end
 -- end

    local function hascolorspec(t)
        for k, v in next, t do
            if find(k,"color") then
                return true
            end
        end
        return false
    end

    -- list of tonumber keywords

    local function initializemps(tfmdata,kind,value)
        if value then
            local specification = settings_to_hash_strict(value)
            if not specification or not next(specification) then
                specification = { category = value }
            end
            -- todo: multiple categories but then maybe also different
            -- clones because of the units .. for now we assume the same
            -- units
            local category = specification.category
            if category and category ~= "" then
                local categories = settings_to_array(category)
                local usedshapes = nil
                local index      = 0
                local spread     = tonumber(specification.spread or 0) -- hm
                local hascolor   = hascolorspec(specification)

                specification.spread = spread -- now a number, maybe also for slant, weight etc

                local preroll = specification.preroll
                if preroll then
                    metapost.simple("simplefun","begingroup;",true,true)
                    metapost.setparameterset("mpsfont",specification)
                    metapost.simple("simplefun",preroll)
                    metapost.setparameterset("mpsfont")
                    metapost.simple("simplefun","endgroup;",true,true)
                end

                for i=1,#categories do
                    local category  = categories[i]
                    local mpsshapes = shapes[category]
                    if mpsshapes then
                        local properties    = tfmdata.properties
                        local parameters    = tfmdata.parameters
                        local characters    = tfmdata.characters
                        local descriptions  = tfmdata.descriptions
                        local mpsparameters = mpsshapes.parameters
                        local units         = mpsparameters.units  or 1000
                        local defaultwidth  = mpsparameters.width  or 0
                        local defaultheight = mpsparameters.height or 0
                        local defaultdepth  = mpsparameters.depth  or 0
                        local usecolor      = mpsparameters.usecolor
                        local spread        = spread * units
                        local defaultcode   = mpsparameters.code or ""
                        local scale         = parameters.size / units
                        if hascolor then
                            -- the graphic has color
                            usecolor = false
                        else
                            -- do whatever is specified
                        end
                        usedshapes = usedshapes or {
                            instance      = "simplefun",
                            units         = units,
                            usecolor      = usecolor,
                            specification = specification,
                            shapes        = mpsshapes,
                        }
                        -- todo: deal with extensibles and more properties
                        for unicode, shape in sortedhash(mpsshapes.glyphs) do
                            index = index + 1 -- todo: somehow we end up with 2 as first entry after 0
                            local wd = shape.width  or defaultwidth
                            local ht = shape.height or defaultheight
                            local dp = shape.depth  or defaultdepth
                            local bb = shape.boundingbox
                            local uc = shape.tounicode -- brrr can be 123.0
                            if type(uc) == "table" then
                                for i=1,#uc do
                                    uc[i] = round(uc[i])
                                end
                            elseif uc then
                                uc = round(uc)
                            end
                            if bb then
                                for i=1,4 do bb[i] = scale * bb[i] end
                            end
                            local newc = {
                                index       = index, -- into usedshapes -- used?
                                width       = scale * (wd + spread),
                                height      = scale * ht,
                                depth       = scale * dp,
                                boundingbox = bb,
                                unicode     = uc or unicode,
                             -- shape       = shape, -- maybe a copy
                            }
                            --
                            characters  [unicode] = newc
                            descriptions[unicode] = newc
                            usedshapes  [unicode] = shape.code or defaultcode
                            --
                            -- This is a way to get/use randomized shapes (see punk example).
                            --
                            if uc and uc ~= unicode then
                                local c = characters[uc]
                                if c then
                                    local v = c.variants
                                    if v then
                                        v[#v + 1] = unicode
                                    else
                                        c.variants = { unicode }
                                    end
                                end
                            end
                        end
                    end
                end
                if usedshapes then
                    -- todo: different font when units and usecolor changes, maybe move into loop
                    -- above
                    dropins.swap("mps",tfmdata,usedshapes)
                end
            end
        end
    end

    -- This kicks in quite late, after features have been checked. So if needed
    -- substitutions need to be defined with force.

    otfregister {
        name         = "metapost",
        description  = "metapost glyphs",
        manipulators = {
            base = initializemps,
            node = initializemps,
        }
    }

end

-- This sits here for historical reasons so for now we keep it here.

local startactualtext = nil
local stopactualtext  = nil

function otf.getactualtext(s)
    if not startactualtext then
        startactualtext = backends.codeinjections.startunicodetoactualtextdirect
        stopactualtext  = backends.codeinjections.stopunicodetoactualtextdirect
    end
    return startactualtext(s), stopactualtext()
end

local sharedpalettes = { }  do

    local colors         <const> = attributes.list[attributes.private('color')] or { }
    local transparencies <const> = attributes.list[attributes.private('transparency')] or { }

    function otf.registerpalette(name,values)
        sharedpalettes[name] = values
        for i=1,#values do
            local v = values[i]
            if v == "textcolor" then
                values[i] = false
            elseif type(v) == "table" then
                values[i] = { kind = "values", data = v }
            else -- freezing
                values[i] = { kind = "attributes", color = colors[v], transparency = transparencies[v] }
            end
        end
    end

end

local initializeoverlay  do

    initializeoverlay = function(tfmdata,kind,value) -- we really need the id ... todo
        if value then
            local resources = tfmdata.resources
            local palettes  = resources.colorpalettes
            if palettes then
                local colorvalues = false
                local colordata = sharedpalettes[value]
                if colordata and #colordata > 0 then
                    colorvalues = {
                        kind = "user",
                        data = colordata,
                    }
                else
                    colordata = palettes[tonumber(value) or 1] or palettes[1]
                    if colordata and #colordata > 0 then
                        colorvalues = {
                            kind = "font",
                            data = colordata,
                        }
                    end
                end
                if colorvalues then
                    local characters   = tfmdata.characters
                    local descriptions = tfmdata.descriptions
                 -- local droppedin, tfmdrop, dropchars, dropdescs, colrshapes
                    local droppedin, tfmdrop, dropchars, colrshapes
                    local idx  = 255
                    local slot = 0
                    --
                    -- maybe delay in which case we have less fonts as we can be sparse
                    --
                    for k, v in next, characters do
                        local index = v.index
                        if index then
                            local description = descriptions[k]
                            if description then
                                local colorlist = description.colors
                                if colorlist then
                                    if idx >= 255 then
                                        idx = 1
                                        colrshapes = { }
                                        slot, droppedin, tfmdrop = fonts.dropins.provide("color",tfmdata,colrshapes,colorvalues)
                                        dropchars = tfmdrop.characters
                                     -- dropdescs = tfmdrop.descriptions
                                    else
                                        idx = idx + 1
                                    end
                                    --
                                    colrshapes[idx] = description
                                    -- todo: use extender
                                    local u = { "use", 0 }
                                    for i=1,#colorlist do
                                        local c = colorlist[i]
                                        if c then
                                            u[i+2] = c.slot
                                        else
                                            -- some error
                                        end
                                    end
                                    v.commands = { u, slotcommand[slot][idx] }
                                    -- hack to prevent that type 3 also gets 'use' flags .. todo
                                    local c = { commands = false, index = idx, dropin = tfmdata }
                                    local d = { } -- index = idx, dropin = tfmdrop
                                    setmetatableindex(c,v)
                                    setmetatableindex(d,description)
                                    dropchars[idx] = c
                                 -- dropdescs[idx] = d -- not needed
                                end
                            end
                        end
                    end
                    return true
                end
            end
        end
    end

    fonts.handlers.otf.features.register {
        name         = "colr",
        description  = "color glyphs",
        manipulators = {
            base = initializeoverlay,
            node = initializeoverlay,
            plug = initializeoverlay,
        }
    }

end

local initializesvg  do

    local report_svg = logs.reporter("fonts","svg")

    local cached = true -- maybe always false (after i've optimized the lot)

    directives.register("fonts.svg.cached", function(v) cached = v end)

    initializesvg = function(tfmdata,kind,value) -- hm, always value
        if value then
            local properties = tfmdata.properties
            local svg        = properties.svg
            local hash       = svg and svg.hash
            local timestamp  = svg and svg.timestamp
            if not hash then
                return
            end
            local shapes   = nil
            local method   = nil
            local fixdepth = value == "fixdepth"
            local enforce  = attributes.colors.model == "cmyk"
            if cached and not enforce then
             -- we need a different hash than for mkiv, so we append:
                local pdfhash   = hash .. "-svg"
                local pdffile   = containers.read(otf.pdfcache,pdfhash)
                local pdfshapes = pdffile and pdffile.pdfshapes
                local pdftarget = file.join(otf.pdfcache.writable,file.addsuffix(pdfhash,"pdf"))
                if not pdfshapes or pdffile.timestamp ~= timestamp or not next(pdfshapes) or not lfs.isfile(pdftarget) then
                    local svgfile   = containers.read(otf.svgcache,hash)
                    local svgshapes = svgfile and svgfile.svgshapes
                    pdfshapes = svgshapes and metapost.svgshapestopdf(svgshapes,pdftarget,report_svg,tfmdata.parameters.units) or { }
                    -- look at ocl: we should store scale and x and y
                    containers.write(otf.pdfcache, pdfhash, {
                        pdfshapes = pdfshapes,
                        timestamp = timestamp,
                    })
                end
                shapes   = pdfshapes
                method   = "pdf"
                fixdepth = true -- needed for svg
            else
                local mpsfile   = containers.read(otf.mpscache,hash)
                local mpsshapes = mpsfile and mpsfile.mpsshapes
                if not mpsshapes or mpsfile.timestamp ~= timestamp or not next(mpsshapes) then
                    local svgfile   = containers.read(otf.svgcache,hash)
                    local svgshapes = svgfile and svgfile.svgshapes
                    -- still suboptimal
                    mpsshapes = svgshapes and metapost.svgshapestomp(svgshapes,report_svg,tfmdata.parameters.units) or { }
                    if enforce then
                        -- cheap conversion, no black component generation
                        mpsshapes.preamble = "interim svgforcecmyk := 1;"
                    end
                    containers.write(otf.mpscache, hash, {
                        mpsshapes = mpsshapes,
                        timestamp = timestamp,
                    })
                end
                shapes = mpsshapes
                method = "mps"
            end
            if shapes then
                shapes.fixdepth = fixdepth
                fonts.dropins.clone(method,tfmdata,shapes)
            end
            return true
        end
    end

    otfregister {
        name         = "svg",
        description  = "svg glyphs",
        manipulators = {
            base = initializesvg,
            node = initializesvg,
            plug = initializesvg,
        }
    }

end

local initializepng  do

    -- If this is really critical we can also use a pdf file as cache but I don't expect
    -- png fonts to remain used.

    local colors = attributes.colors

    local report_png = logs.reporter("fonts","png conversion")

    initializepng = function(tfmdata,kind,value) -- hm, always value
        if value then
            local properties = tfmdata.properties
            local png        = properties.png
            local hash       = png and png.hash
            local timestamp  = png and png.timestamp
            if not hash then
                return
            end
            local pngfile    = containers.read(otf.pngcache,hash)
            local pngshapes  = pngfile and pngfile.pngshapes
            if pngshapes then
                if colors.model == "cmyk" then
                    pngshapes.enforcecmyk = true
                end
                fonts.dropins.clone("png",tfmdata,pngshapes)
            end
            return true
        end
    end

    otfregister {
        name         = "sbix",
        description  = "sbix glyphs",
        manipulators = {
            base = initializepng,
            node = initializepng,
            plug = initializepng,
        }
    }

    otfregister {
        name         = "cblc",
        description  = "cblc glyphs",
        manipulators = {
            base = initializepng,
            node = initializepng,
            plug = initializepng,
        }
    }

end

do

    -- I need to check jpeg and such but will do that when I run into
    -- it.

    local function initializecolor(tfmdata,kind,value)
        if value == "auto" then
            return
                initializeoverlay(tfmdata,kind,value) or
                initializesvg(tfmdata,kind,value) or
                initializepng(tfmdata,kind,value)
        elseif value == "overlay" then
            return initializeoverlay(tfmdata,kind,value)
        elseif value == "svg" then
            return initializesvg(tfmdata,kind,value)
        elseif value == "png" or value == "bitmap" then
            return initializepng(tfmdata,kind,value)
        else
            -- forget about it
        end
    end

    otfregister {
        name         = "color",
        description  = "color glyphs",
        manipulators = {
            base = initializecolor,
            node = initializecolor,
            plug = initializecolor,
        }
    }

end
