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

if not context then os.exit() end -- generic function in node-dum

local next, type = next, type
local concat = table.concat

local nodes, node, fonts = nodes, node, fonts

local trace_characters  = false  trackers.register("nodes.characters", function(v) trace_characters = v end)
local trace_fontrun     = false  trackers.register("nodes.fontrun",    function(v) trace_fontrun    = v end)

local report_fonts      = logs.reporter("fonts","processing")

local fonthashes        = fonts.hashes
local fontdata          = fonthashes.identifiers

local otf               = fonts.handlers.otf

local traverse_id       = node.traverse_id
local has_attribute     = node.has_attribute
local starttiming       = statistics.starttiming
local stoptiming        = statistics.stoptiming
local nodecodes         = nodes.nodecodes
local handlers          = nodes.handlers

local glyph_code        = nodecodes.glyph

local setmetatableindex = table.setmetatableindex

-- some tests with using an array of dynamics[id] and processes[id] demonstrated
-- that there was nothing to gain (unless we also optimize other parts)
--
-- maybe getting rid of the intermediate shared can save some time

-- potential speedup: check for subtype < 256 so that we can remove that test
-- elsewhere, danger: injected nodes will not be dealt with but that does not
-- happen often; we could consider processing sublists but that might need mor
-- checking later on; the current approach also permits variants

local run = 0

local setfontdynamics = { }
local fontprocesses   = { }

setmetatableindex(setfontdynamics, function(t,font)
    local tfmdata = fontdata[font]
    local shared = tfmdata.shared
    local v = shared and shared.dynamics and otf.setdynamics or false
    t[font] = v
    return v
end)

setmetatableindex(fontprocesses, function(t,font)
    local tfmdata = fontdata[font]
    local shared = tfmdata.shared -- we need to check shared, only when same features
    local processes = shared and shared.processes
    if processes and #processes > 0 then
        t[font] = processes
        return processes
    else
        t[font] = false
        return false
    end
end)

fonts.hashes.setdynamics = setfontdynamics
fonts.hashes.processes   = fontprocesses

function handlers.characters(head)
    -- either next or not, but definitely no already processed list
    starttiming(nodes)
    local usedfonts, attrfonts, done = { }, { }, false
    local a, u, prevfont, prevattr = 0, 0, nil, 0
    if trace_fontrun then
        run = run + 1
        report_fonts()
        report_fonts("checking node list, run %s",run)
        report_fonts()
        local n = head
        while n do
            if n.id == glyph_code then
                local font = n.font
                local attr = has_attribute(n,0) or 0
                report_fonts("font %03i, dynamic %03i, glyph %s",font,attr,utf.char(n.char))
            else
                report_fonts("[%s]",nodecodes[n.id])
            end
            n = n.next
        end
    end
    for n in traverse_id(glyph_code,head) do
        local font, attr = n.font, has_attribute(n,0) or 0 -- zero attribute is reserved for fonts in context
        if font ~= prevfont or attr ~= prevattr then
            if attr > 0 then
                local used = attrfonts[font]
                if not used then
                    used = { }
                    attrfonts[font] = used
                end
                if not used[attr] then
                    local sd = setfontdynamics[font]
                    if sd then -- always true ?
                        local d = sd(font,attr) -- can we cache this one?
                        if d then
                            used[attr] = d
                            a = a + 1
                        end
                    end
                end
            else
                local used = usedfonts[font]
                if not used then
                    local fp = fontprocesses[font]
                    if fp then
                        usedfonts[font] = fp
                        u = u + 1
                    end
                end
            end
            prevfont = font
            prevattr = attr
        end
    end
    if trace_fontrun then
        report_fonts()
        report_fonts("statics : %s",(u > 0 and concat(table.keys(usedfonts)," ")) or "none")
        report_fonts("dynamics: %s",(a > 0 and concat(table.keys(attrfonts)," ")) or "none")
        report_fonts()
    end
    -- we could combine these and just make the attribute nil
    if u == 1 then
        local font, processors = next(usedfonts)
        local n = #processors
        if n > 0 then
            local h, d = processors[1](head,font,0)
            head = h or head
            done = done or d
            if n > 1 then
                for i=2,n do
                    local h, d = processors[i](head,font,0)
                    head = h or head
                    done = done or d
                end
            end
        end
    elseif u > 0 then
        for font, processors in next, usedfonts do
            local n = #processors
            local h, d = processors[1](head,font,0)
            head = h or head
            done = done or d
            if n > 1 then
                for i=2,n do
                    local h, d = processors[i](head,font,0)
                    head = h or head
                    done = done or d
                end
            end
        end
    end
    if a == 1 then
        local font, dynamics = next(attrfonts)
        for attribute, processors in next, dynamics do -- attr can switch in between
            local n = #processors
            if n == 0 then
                report_fonts("no processors associated with dynamic %s",attribute)
            else
                local h, d = processors[1](head,font,attribute)
                head = h or head
                done = done or d
                if n > 1 then
                    for i=2,n do
                        local h, d = processors[i](head,font,attribute)
                        head = h or head
                        done = done or d
                    end
                end
            end
        end
    elseif a > 0 then
        for font, dynamics in next, attrfonts do
            for attribute, processors in next, dynamics do -- attr can switch in between
                local n = #processors
                if n == 0 then
                    report_fonts("no processors associated with dynamic %s",attribute)
                else
                    local h, d = processors[1](head,font,attribute)
                    head = h or head
                    done = done or d
                    if n > 1 then
                        for i=2,n do
                            local h, d = processors[i](head,font,attribute)
                            head = h or head
                            done = done or d
                        end
                    end
                end
            end
        end
    end
    stoptiming(nodes)
    if trace_characters then
        nodes.report(head,done)
    end
    return head, true
end

handlers.protectglyphs   = node.protect_glyphs
handlers.unprotectglyphs = node.unprotect_glyphs
