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

-- we can remap fonts

local fontproperties    = fonts.hashes.properties
local fontparameters    = fonts.hashes.parameters
local fontshapes        = fonts.hashes.shapes

local starttiming       = statistics.starttiming
local stoptiming        = statistics.stoptiming

local texgetbox         = tex.getbox

local bpfactor          <const> = number.dimenfactors.bp

local rulecodes         <const> = nodes.rulecodes
local normalrule_code   <const> = rulecodes.normal
----- boxrule_code      <const> = rulecodes.box
----- imagerule_code    <const> = rulecodes.image
----- emptyrule_code    <const> = rulecodes.empty
----- userrule_code     <const> = rulecodes.user
----- overrule_code     <const> = rulecodes.over
----- underrule_code    <const> = rulecodes.under
----- fractionrule_code <const> = rulecodes.fraction
----- radicalrule_code  <const> = rulecodes.radical
local outlinerule_code  <const> = rulecodes.outline

-- todo : per instance

local pages     = { }
local fonts     = { }
local names     = { }
local mapping   = { }
local used      = { }
local shapes    = { }
local glyphs    = { }
local buffer    = { }
local metadata  = nil
local b         = 0
local converter = nil

local compact   = false -- true
local shapestoo = true

local x, y, d, f, c, w, h, t, r, o

local function reset()
    buffer = { }
    b      = 0
    t      = nil
    x      = nil
    y      = nil
    d      = nil
    f      = nil
    c      = nil
    w      = nil
    h      = nil
    r      = nil
    o      = nil
end

local function result()
    return {
        metadata = metadata,
        fonts    = fonts,
        pages    = pages,
        shapes   = shapes,
    }
end

-- actions

local function outputfilename(driver)
    return tex.jobname .. "-output.lua"
end

local function save() -- might become a driver function that already is plugged into stopactions
    local filename = outputfilename()
    drivers.report("saving result in %a",filename)
    starttiming(drivers)
    local data = result()
    if data then
        io.savedata(filename,table.serialize(data))
    end
    stoptiming(drivers)
end

local function prepare(driver)
    converter = drivers.converters.lmtx
 -- if not environment.initex then
        backends.initialize("lua")
 -- end
    luatex.registerstopactions(1,function()
        save()
    end)
end

local function initialize(driver,details)
    reset()
end

local function finalize(driver,details)
    local n = details.pagenumber
    local b = details.boundingbox
    pages[n] = {
        number      = n,
        content     = buffer,
        boundingbox = {
            b[1] * bpfactor,
            b[2] * bpfactor,
            b[3] * bpfactor,
            b[4] * bpfactor,
        },
    }
    if not metadata then
        -- this has to happen while we're still running tex because we do
        -- prerolls
        local identity = interactions.general.getidentity()
        local jobname  = environment.jobname or tex.jobname or "unknown"
        metadata = {
            unit     = "bp",
            jobname  = jobname,
            title    = identity.title,
            subject  = identity.subject,
            author   = identity.author,
            keywords = identity.keywords,
            time     = os.date("%Y-%m-%d %H:%M"),
            engine   = environment.luatexengine .. " " .. environment.luatexversion,
            context  = environment.version,
        }
    end
end

local function wrapup(driver)
end

local function cleanup(driver)
    reset()
end

local function convert(driver,boxnumber,pagenumber)
    converter(driver,texgetbox(boxnumber),"page",pagenumber)
end

-- flushers

local function updatefontstate(id)
    if not mapping[id] then
        local fn = #fonts + 1
        mapping[id] = fn
        local properties = fontproperties[id]
        local parameters = fontparameters[id]
        local filename   = file.basename(properties.filename)
        local fontname   = properties.fullname or properties.fontname
        if shapestoo then
            if not names[fontname] then
                local sn = #shapes+1
                names[fontname] = sn
                shapes[sn] = { }
                glyphs[sn] = fontshapes[id].glyphs
            end
        end
        fonts[fn] = {
            filename = filename,
            name     = fontname,
            size     = parameters.size * bpfactor,
            shapes   = shapestoo and names[fontname] or nil,
        }
    end
end

local function flushcharacter(current, pos_h, pos_v, pos_r, font, char)
    local fnt = mapping[font]
    b = b + 1
    buffer[b] = {
        t = "glyph" ~= t and "glyph" or nil,
        f = font    ~= f and fnt or nil,
        c = char    ~= c and char or nil,
        x = pos_h   ~= x and (pos_h * bpfactor) or nil,
        y = pos_v   ~= y and (pos_v * bpfactor) or nil,
        d = pos_r   ~= d and (pos_r == 1 and "r2l" or "l2r") or nil,
    }
    t = "glyph"
    f = font
    c = char
    x = pos_h
    y = pos_v
    d = pos_r
    if shapestoo then
        -- can be sped up if needed
        local sn = fonts[fnt].shapes
        local shp = shapes[sn]
        if not shp[char] then
            shp[char] = glyphs[sn][char]
        end
    end
end

local function flush_rule(current, pos_h, pos_v, pos_r, size_h, size_v, rule_s, rule_o)
    b = b + 1
    buffer[b] = {
        t = "rule" ~= t and "rule" or nil,
        r = rule_s ~= r and rule_s or nil,
        o = rule_s == "outline" and rule_o ~= o and (rule_o * bpfactor) or nil,
        w = size_h ~= w and (size_h * bpfactor) or nil,
        h = size_v ~= h and (size_v * bpfactor) or nil,
        x = pos_h  ~= x and (pos_h  * bpfactor) or nil,
        y = pos_v  ~= y and (pos_v  * bpfactor) or nil,
        d = pos_r  ~= d and (pos_r == 1 and "r2l" or "l2r") or nil,
    }
    t = "rule"
    w = size_h
    h = size_v
    x = pos_h
    y = pos_v
    d = pos_r
end

local function flushrule(current, pos_h, pos_v, pos_r, size_h, size_v, subtype)
    local rule_s, rule_o
    if subtype == normalrule_code then
        rule_s = normal_rule_code
    elseif subtype == outlinerule_code then
        rule_s = outline_rule_code
        rule_o = getdata(current)
    else
        return
    end
    return flush_rule(pos_h, pos_v, pos_r, size_h, size_v, rule_s, rule_o)
end

local function flushsimplerule(pos_h, pos_v, pos_r, size_h, size_v)
    return flush_rule(false,pos_h,pos_v,pos_r,size_h,size_v,normalrule_code,nil)
end

local function flushspecialrule(pos_h, pos_v, pos_r, w, h, d, l, outline)
    return flush_rule(false,pos_h,pos_v-d,pos_r,w,h+d,outline and outlinerule_code or normalrule_code)
end

-- file stuff too
-- todo: default flushers
-- also color (via hash)

-- installer

drivers.install {
    name    = "lua",
    actions = {
        prepare         = prepare,
        initialize      = initialize,
        finalize        = finalize,
        wrapup          = wrapup,
        cleanup         = cleanup,
        convert         = convert,
        outputfilename  = outputfilename,
    },
    flushers = {
        updatefontstate = updatefontstate,
        character       = flushcharacter,
        rule            = flushrule,
        simplerule      = flushsimplerule,
        specialrule     = flushspecialrule,
    }
}

-- actions

local function outputfilename(driver)
    return tex.jobname .. "-output.json"
end

local function save() -- might become a driver function that already is plugged into stopactions
    local filename = outputfilename()
    drivers.report("saving result in %a",filename)
    starttiming(drivers)
    local data = result()
    if data then
        if not utilities.json then
            require("util-jsn")
        end
        io.savedata(filename,utilities.json.tostring(data,not compact))
    end
    stoptiming(drivers)
end

local function prepare(driver)
    converter = drivers.converters.lmtx
    luatex.registerstopactions(1,function()
        save()
    end)
end

-- installer

drivers.install {
    name    = "json",
    actions = {
        prepare         = prepare,
        wrapup          = wrapup,
        cleanup         = cleanup,
        --
        initialize      = initialize,
        convert         = convert,
        finalize        = finalize,
        --
        outputfilename  = outputfilename,
    },
    flushers = {
        updatefontstate = updatefontstate,
        character       = flushcharacter,
        rule            = flushrule,
        simplerule      = flushsimplerule,
        specialrule     = flushspecialrule,
        setstate        = function() end,
    }
}

-- actions

local function outputfilename(driver)
    return tex.jobname .. "-output.js"
end

local function save() -- might become a driver function that already is plugged into stopactions
    local filename = outputfilename()
    drivers.report("saving result in %a",filename)
    starttiming(drivers)
    local data = result()
    if data then
        if not utilities.json then
            require("util-jsn")
        end
        io.savedata(filename,
            "const JSON_CONTEXT = JSON.parse ( `" ..
            utilities.json.tostring(data,not compact) ..
            "` ) ;\n"
        )
    end
    stoptiming(drivers)
end

local function prepare(driver)
    converter = drivers.converters.lmtx
 -- if not environment.initex then
        backends.initialize("js")
 -- end
    luatex.registerstopactions(1,function()
        save()
    end)
end

-- installer

drivers.install {
    name    = "js",
    actions = {
        prepare         = prepare,
        initialize      = initialize,
        finalize        = finalize,
        wrapup          = wrapup,
        cleanup         = cleanup,
        convert         = convert,
        outputfilename  = outputfilename,
    },
    flushers = {
        updatefontstate = updatefontstate,
        character       = flushcharacter,
        rule            = flushrule,
        simplerule      = flushsimplerule,
        specialrule     = flushspecialrule,
    }
}
