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

-- todo:
--
-- * autoleft/right depending on available space (or distance to margin)
-- * stack across paragraphs, but that is messy and one should reconsider
--   using margin data then as also vertical spacing kicks in
-- * floating margin data, with close-to-call anchoring

-- -- experiment (does not work, too much interference)
--
-- local pdfprint = pdf.print
-- local format = string.format
--
-- anchors = anchors or { }
--
-- local whatever = { }
-- local factor   = (7200/7227)/65536
--
-- function anchors.set(tag)
--     whatever[tag] = { pdf.h, pdf.v }
-- end
--
-- function anchors.reset(tag)
--     whatever[tag] = nil
-- end
--
-- function anchors.startmove(tag,how) -- save/restore nodes but they don't support moves
--     local w = whatever[tag]
--     if not w then
--         -- error
--     elseif how == "horizontal" or how == "h" then
--         pdfprint("page",format(" q 1 0 0 1 %f 0 cm ", (w[1] - pdf.h) * factor))
--     elseif how == "vertical" or how == "v" then
--         pdfprint("page",format(" q 1 0 0 1 0 %f cm ", (w[2] - pdf.v) * factor))
--     else
--         pdfprint("page",format(" q 1 0 0 1 %f %f cm ", (w[1] - pdf.h) * factor, (w[2] - pdf.v) * factor))
--     end
-- end
--
-- function anchors.stopmove(tag)
--     local w = whatever[tag]
--     if not w then
--         -- error
--     else
--         pdfprint("page"," Q ")
--     end
-- end
--
-- local latelua = nodes.pool.latelua
--
-- function anchors.node_set(tag)
--     return latelua(formatters["anchors.set(%q)"](tag))
-- end
--
-- function anchors.node_reset(tag)
--     return latelua(formatters["anchors.reset(%q)"](tag))
-- end
--
-- function anchors.node_start_move(tag,how)
--     return latelua(formatters["anchors.startmove(%q,%q)](tag,how))
-- end
--
-- function anchors.node_stop_move(tag)
--     return latelua(formatters["anchors.stopmove(%q)"](tag))
-- end

-- so far

local format, validstring = string.format, string.valid
local insert, remove = table.insert, table.remove
local setmetatable, next = setmetatable, next

local attributes, nodes, node, variables = attributes, nodes, node, variables

local trace_margindata  = false  trackers.register("typesetters.margindata",       function(v) trace_margindata  = v end)
local trace_marginstack = false  trackers.register("typesetters.margindata.stack", function(v) trace_marginstack = v end)
local trace_margingroup = false  trackers.register("typesetters.margindata.group", function(v) trace_margingroup = v end)

local report_margindata = logs.reporter("typesetters","margindata")

local tasks              = nodes.tasks
local prependaction      = tasks.prependaction
local disableaction      = tasks.disableaction
local enableaction       = tasks.enableaction

local variables          = interfaces.variables

local conditionals       = tex.conditionals
local systemmodes        = tex.systemmodes

local v_top              = variables.top
local v_depth            = variables.depth
local v_local            = variables["local"]
local v_global           = variables["global"]
local v_left             = variables.left
local v_right            = variables.right
local v_flushleft        = variables.flushleft
local v_flushright       = variables.flushright
local v_inner            = variables.inner
local v_outer            = variables.outer
local v_margin           = variables.margin
local v_edge             = variables.edge
local v_default          = variables.default
local v_normal           = variables.normal
local v_yes              = variables.yes
local v_continue         = variables.continue
local v_first            = variables.first
local v_text             = variables.text
local v_column           = variables.column

local copy_node_list     = node.copy_list
local slide_nodes        = node.slide
local hpack_nodes        = node.hpack -- nodes.fasthpack not really faster here
local traverse_id        = node.traverse_id
local free_node_list     = node.flush_list
local insert_node_after  = node.insert_after
local insert_node_before = node.insert_before

local concat_nodes       = nodes.concat

local nodecodes          = nodes.nodecodes
local listcodes          = nodes.listcodes
local gluecodes          = nodes.gluecodes
local whatsitcodes       = nodes.whatsitcodes

local hlist_code         = nodecodes.hlist
local vlist_code         = nodecodes.vlist
local glue_code          = nodecodes.glue
local kern_code          = nodecodes.kern
local penalty_code       = nodecodes.penalty
local whatsit_code       = nodecodes.whatsit
local line_code          = listcodes.line
local cell_code          = listcodes.cell
local alignment_code     = listcodes.alignment
local leftskip_code      = gluecodes.leftskip
local rightskip_code     = gluecodes.rightskip
local userdefined_code   = whatsitcodes.userdefined

local dir_code           = whatsitcodes.dir
local localpar_code      = whatsitcodes.localpar

local nodepool           = nodes.pool

local new_kern           = nodepool.kern
local new_glue           = nodepool.glue
local new_penalty        = nodepool.penalty
local new_stretch        = nodepool.stretch
local new_usernumber     = nodepool.usernumber
local new_latelua        = nodepool.latelua

local texcount           = tex.count
local texdimen           = tex.dimen
local texbox             = tex.box

local points             = number.points

local isleftpage         = layouts.status.isleftpage
local registertogether   = builders.paragraphs.registertogether

local jobpositions       = job.positions
local getposition        = jobpositions.position

local a_margindata       = attributes.private("margindata")

local inline_mark        = nodepool.userids["margins.inline"]

local margins            =  { }
typesetters.margins      = margins

local locations          = { v_left, v_right, v_inner, v_outer } -- order might change
local categories         = { }
local displaystore       = { } -- [category][location][scope]
local inlinestore        = { } -- [number]
local nofsaved           = 0
local nofstored          = 0
local nofinlined         = 0
local nofdelayed         = 0
local h_anchors          = 0
local v_anchors          = 0

local mt1 = {
    __index = function(t,location)
        local v = { [v_local] = { }, [v_global] = { } }
        t[location] = v
        return v
    end
}

local mt2 = {
    __index = function(stores,category)
        categories[#categories+1] = category
        local v = { }
        setmetatable(v,mt1)
        stores[category] = v
        return v
    end
}

setmetatable(displaystore,mt2)

local defaults = {
    __index  = {
        location  = v_left,
        align     = v_normal,
        method    = "",
        name      = "",
        threshold = 0, -- .25ex
        margin    = v_normal,
        scope     = v_global,
        distance  = 0,
        hoffset   = 0,
        voffset   = 0,
        category  = v_default,
        line      = 0,
        vstack    = 0,
        dy        = 0,
        baseline  = false,
        inline    = false,
        leftskip  = 0,
        rightskip = 0,
    }
}

local enablelocal, enableglobal -- forward reference (delayed initialization)

local function showstore(store,banner,location)
    if next(store) then
        for i, si in table.sortedpairs(store) do
            local si =store[i]
            report_margindata("%s: stored in %a at %s: %a => %s",banner,location,i,validstring(si.name,"no name"),nodes.toutf(si.box.list))
        end
    else
        report_margindata("%s: nothing stored in location %a",banner,location)
    end
end

function margins.save(t)
    setmetatable(t,defaults)
    local content  = texbox[t.number]
    local location = t.location
    local category = t.category
    local inline   = t.inline
    local scope    = t.scope or v_global
    if not content then
        report_margindata("ignoring empty margin data %a",location or "unknown")
        return
    end
    local store
    if inline then
        store = inlinestore
    else
        store = displaystore[category][location]
        if not store then
            report_margindata("invalid location %a",location)
            return
        end
        store = store[scope]
    end
    if not store then
        report_margindata("invalid scope %a",scope)
        return
    end
    if enablelocal and scope == v_local then
        enablelocal()
        if enableglobal then
            enableglobal() -- is the fallback
        end
    elseif enableglobal and scope == v_global then
        enableglobal()
    end
    nofsaved = nofsaved + 1
    nofstored = nofstored + 1
    local name = t.name
    if trace_marginstack then
        showstore(store,"before",location)
    end
    if name and name ~= "" then
        if inlinestore then -- todo: inline store has to be done differently (not sparse)
            local t = table.sortedkeys(store) for j=#t,1,-1 do local i = t[j]
                local si = store[i]
                if si.name == name then
                    local s = remove(store,i)
                    free_node_list(s.box)
                end
            end
        else
            for i=#store,1,-1 do
                local si = store[i]
                if si.name == name then
                    local s = remove(store,i)
                    free_node_list(s.box)
                end
            end
        end
        if trace_marginstack then
            showstore(store,"between",location)
        end
    end
    if t.number then
        -- better make a new table and make t entry in t
        t.box                 = copy_node_list(content)
        t.n                   = nofsaved
        -- used later (we will clean up this natural mess later)
        -- nice is to make a special status table mechanism
        local leftmargindistance  = texdimen.naturalleftmargindistance
        local rightmargindistance = texdimen.naturalrightmargindistance
        t.strutdepth          = texbox.strutbox.depth
        t.strutheight         = texbox.strutbox.height
        t.leftskip            = tex.leftskip.width  -- we're not in forgetall
        t.rightskip           = tex.rightskip.width -- we're not in forgetall
        t.leftmargindistance  = leftmargindistance -- todo:layoutstatus table
        t.rightmargindistance = rightmargindistance
        t.leftedgedistance    = texdimen.naturalleftedgedistance
                              + texdimen.leftmarginwidth
                              + leftmargindistance
        t.rightedgedistance   = texdimen.naturalrightedgedistance
                              + texdimen.rightmarginwidth
                              + rightmargindistance
        t.lineheight          = texdimen.lineheight
        --
     -- t.realpageno          = texcount.realpageno
        if inline then
            context(new_usernumber(inline_mark,nofsaved))
            store[nofsaved] = t -- no insert
            nofinlined = nofinlined + 1
        else
            insert(store,t)
        end
    end
    if trace_marginstack then
        showstore(store,"after",location)
    end
    if trace_margindata then
        report_margindata("saved %a, location %a, scope %a, inline %a",nofsaved,location,scope,inline)
    end
end

-- Actually it's an advantage to have them all anchored left (tags and such)
-- we could keep them in store and flush in stage two but we might want to
-- do more before that so we need the content to be there unless we can be
-- sure that we flush this first which might not be the case in the future.
--
-- When the prototype inner/outer code that was part of this proved to be
-- okay it was moved elsewhere.

local status, nofstatus = { }, 0

local function realign(current,candidate)
    local location      = candidate.location
    local margin        = candidate.margin
    local hoffset       = candidate.hoffset
    local distance      = candidate.distance
    local hsize         = candidate.hsize
    local width         = candidate.width
    local align         = candidate.align
 -- local realpageno    = candidate.realpageno
    local leftpage      = isleftpage(false,true)
    local delta         = 0
    local leftdelta     = 0
    local rightdelta    = 0
    local leftdistance  = distance
    local rightdistance = distance
    if margin == v_normal then
        --
    elseif margin == v_local then
        leftdelta  = - candidate.leftskip
        rightdelta =   candidate.rightskip
    elseif margin == v_margin then
        leftdistance  = candidate.leftmargindistance
        rightdistance = candidate.rightmargindistance
    elseif margin == v_edge then
        leftdistance  = candidate.leftedgedistance
        rightdistance = candidate.rightedgedistance
    end
    if leftpage then
        leftdistance, rightdistance = rightdistance, leftdistance
    end

    if location == v_left then
        delta =  hoffset + width + leftdistance  + leftdelta
    elseif location == v_right then
        delta = -hoffset - hsize - rightdistance + rightdelta
    elseif location == v_inner then
        if leftpage then
            delta = -hoffset - hsize - rightdistance + rightdelta
        else
            delta =  hoffset + width + leftdistance  + leftdelta
        end
    elseif location == v_outer then
        if leftpage then
            delta =  hoffset + width + leftdistance  + leftdelta
        else
            delta = -hoffset - hsize - rightdistance + rightdelta
        end
    end

    -- we assume that list is a hbox, otherwise we had to take the whole current
    -- in order to get it right

    current.width = 0
    local anchornode, move_x

    -- this mess is needed for alignments (combinations) so we use that
    -- oportunity to add arbitrary anchoring

    -- always increment anchor is nicer for multipass when we add new ..

    local inline = candidate.inline
    local anchor = candidate.anchor
    if not anchor or anchor == "" then
        anchor = v_text
    end
    if inline or anchor ~= v_text or candidate.psubtype == alignment_code then
        -- the alignment_code check catches margintexts ste before a tabulate
        h_anchors = h_anchors + 1
        anchornode = new_latelua(format("_plib_.set('md:h',%i,{x=true,c=true})",h_anchors))
        local blob = jobpositions.get('md:h', h_anchors)
        if blob then
            local reference = jobpositions.getreserved(anchor,blob.c)
            if reference then
                if location == v_left then
                    move_x = (reference.x or 0) - (blob.x or 0)
                elseif location == v_right then
                    move_x = (reference.x or 0) - (blob.x or 0) + (reference.w or 0) - hsize
                else
                    -- not yet done
                end
            end
        end
    end

    if move_x then
        delta = delta - move_x
        if trace_margindata then
            report_margindata("realigned %a, location %a, margin %a, move %p",candidate.n,location,margin,move_x)
        end
    else
        if trace_margindata then
            report_margindata("realigned %a, location %a, margin %a",candidate.n,location,margin)
        end
    end

    current.list = hpack_nodes(concat_nodes{anchornode,new_kern(-delta),current.list,new_kern(delta)})
    current.width = 0
end

local function realigned(current,a)
    local candidate = status[a]
    realign(current,candidate)
    nofdelayed = nofdelayed - 1
    status[a] = nil
    return true
end

-- Stacking is done in two ways: the v_yes option stacks per paragraph (or line,
-- depending on what gets by) and mostly concerns margin data dat got set at more or
-- less the same time. The v_continue option uses position tracking and works on
-- larger range. However, crossing pages is not part of it. Anyway, when you have
-- such messed up margin data you'd better think twice.
--
-- The stacked table keeps track (per location) of the offsets (the v_yes case). This
-- table gets saved when the v_continue case is active. We use a special variant
-- of position tracking, after all we only need the page number and vertical position.

local stacked = { } -- left/right keys depending on location
local cache   = { }

local function resetstacked()
    stacked = { }
end

-- resetstacked()

function margins.ha(tag) -- maybe l/r keys ipv left/right keys
    local p = cache[tag]
    p.p = true
    p.y = true
    jobpositions.set('md:v',tag,p)
    cache[tag] = nil
end

local function markovershoot(current)
    v_anchors = v_anchors + 1
    cache[v_anchors] = stacked
    local anchor = new_latelua(format("typesetters.margins.ha(%s)",v_anchors)) -- todo: alleen als offset > line
    current.list = hpack_nodes(concat_nodes{anchor,current.list})
end

local function getovershoot(location)
    local p = jobpositions.get("md:v",v_anchors)
    local c = jobpositions.get("md:v",v_anchors+1)
    if p and c and p.p and p.p == c.p then
        local distance = p.y - c.y
        local offset = p[location] or 0
        local overshoot = offset - distance
        if trace_marginstack then
            report_margindata("location %a, distance %p, offset %p, overshoot %p",location,distance,offset,overshoot)
        end
        if overshoot > 0 then
            return overshoot
        end
    end
    return 0
end

local function inject(parent,head,candidate)
    local box          = candidate.box
    local width        = box.width
    local height       = box.height
    local depth        = box.depth
    local shift        = box.shift
    local stack        = candidate.stack
    local location     = candidate.location
    local method       = candidate.method
    local voffset      = candidate.voffset
    local line         = candidate.line
    local baseline     = candidate.baseline
    local strutheight  = candidate.strutheight
    local strutdepth   = candidate.strutdepth
    local psubtype     = parent.subtype
    local offset       = stacked[location]
    local firstonstack = offset == false or offset == nil
    nofstatus          = nofstatus  + 1
    nofdelayed         = nofdelayed + 1
    status[nofstatus]  = candidate
    -- yet untested
    if baseline == true then
        baseline = false
        -- hbox vtop
--~         for h in traverse_id(hlist_code,box.list.list) do
--~             baseline = h.height
--~             break
--~         end
    else
        baseline = tonumber(baseline)
        if not baseline or baseline <= 0 then
            -- in case we have a box of width 0 that is not analyzed
            baseline = false -- strutheight -- actually a hack
        end
    end
    candidate.width = width
    candidate.hsize = parent.width -- we can also pass textwidth
    candidate.psubtype = psubtype
    if trace_margindata then
        report_margindata("processing, index %s, height %p, depth %p, parent %s",candidate.n,height,depth,listcodes[psubtype])
    end
    if firstonstack then
        offset = 0
    else
--         offset = offset + height
    end
    if stack == v_yes then
        offset = offset + candidate.dy
        shift = shift + offset
    elseif stack == v_continue then
        offset = offset + candidate.dy
        if firstonstack then
            offset = offset + getovershoot(location)
        end
        shift = shift + offset
    end
    -- -- --
    -- Maybe we also need to patch offset when we apply methods, but how ...
    -- This needs a bit of playing as it depends on the stack setting of the
    -- following which we don't know yet ... so, consider stacking partially
    -- experimental.
    -- -- --
    if method == v_top then
        local delta = height - parent.height
        if trace_margindata then
            report_margindata("top aligned by %p",delta)
        end
        if delta < candidate.threshold then
            shift = shift + voffset + delta
        end
    elseif method == v_first then
        if baseline then
            shift = shift + voffset + height - baseline -- option
        else
            shift = shift + voffset -- normal
        end
        if trace_margindata then
            report_margindata("first aligned")
        end
    elseif method == v_depth then
        local delta = strutdepth
        if trace_margindata then
            report_margindata("depth aligned by %p",delta)
        end
        shift = shift + voffset + delta
    elseif method == v_height then
        local delta = - strutheight
        if trace_margindata then
            report_margindata("height aligned by %p",delta)
        end
        shift = shift + voffset + delta
    elseif voffset ~= 0 then
        if trace_margindata then
            report_margindata("voffset %p applied",voffset)
        end
        shift = shift + voffset
    end
    -- -- --
    if line ~= 0 then
        local delta = line * candidate.lineheight
        if trace_margindata then
            report_margindata("offset %p applied to line %s",delta,line)
        end
        shift = shift + delta
        offset = offset + delta
    end
    box.shift = shift
    box.width = 0
    if not head then
        head = box
    elseif head.id == whatsit_code and head.subtype == localpar_code then
        -- experimental
        if head.dir == "TRT" then
            box.list = hpack_nodes(concat_nodes{new_kern(candidate.hsize),box.list,new_kern(-candidate.hsize)})
        end
        insert_node_after(head,head,box)
    else
        head.prev = box
        box.next = head
        head = box
    end
    box[a_margindata] = nofstatus
    if trace_margindata then
        report_margindata("injected, location %a, shift %p",location,shift)
    end
    -- we need to add line etc to offset as well
    offset = offset + depth
    local room = {
        height     = height,
        depth      = offset,
        slack      = candidate.bottomspace, -- todo: 'depth' => strutdepth
        lineheight = candidate.lineheight,  -- only for tracing
    }
    offset = offset + height
    stacked[location] = offset -- weird, no table ?
    -- todo: if no real depth then zero
    if trace_margindata then
        report_margindata("status, offset %s",offset)
    end
    return head, room, stack == v_continue
end

local function flushinline(parent,head)
    local current = head
    local done = false
    local continue = false
    local room, don, con
    while current and nofinlined > 0 do
        local id = current.id
        if id == whatsit_code then
            if current.subtype == userdefined_code and current.user_id == inline_mark then
                local n = current.value
                local candidate = inlinestore[n]
                if candidate then -- no vpack, as we want to realign
                    inlinestore[n] = nil
                    nofinlined = nofinlined - 1
                    head, room, con = inject(parent,head,candidate) -- maybe return applied offset
                    continue = continue or con
                    done = true
                    nofstored = nofstored - 1
                end
            end
        elseif id == hlist_code or id == vlist_code then
            -- optional (but sometimes needed)
            current.list, don, con = flushinline(current,current.list)
            continue = continue or con
            done = done or don
        end
        current = current.next
    end
    return head, done, continue
end

local a_linenumber = attributes.private('linenumber')

local function flushed(scope,parent) -- current is hlist
    local head = parent.list
    local done = false
    local continue = false
    local room, con, don
    for c=1,#categories do
        local category = categories[c]
        for l=1,#locations do
            local location = locations[l]
            local store = displaystore[category][location][scope]
            while true do
                local candidate = remove(store,1) -- brr, local stores are sparse
                if candidate then -- no vpack, as we want to realign
                    head, room, con = inject(parent,head,candidate)
                    done = true
                    continue = continue or con
                    nofstored = nofstored - 1
                    registertogether(parent,room)
                else
                    break
                end
            end
        end
    end
    if nofinlined > 0 then
        if done then
            parent.list = head
        end
        head, don, con = flushinline(parent,head)
        continue = continue or con
        done = done or don
    end
    if done then
        local a = head[a_linenumber] -- hack .. we need a more decent critical attribute inheritance mechanism
        parent.list = hpack_nodes(head,parent.width,"exactly")
        if a then
            parent.list[a_linenumber] = a
        end
     -- resetstacked()
    end
    return done, continue
end

-- only when group   : vbox|vmode_par
-- only when subtype : line, box (no indent alignment cell)

local function handler(scope,head,group)
   if nofstored > 0 then
        if trace_margindata then
            report_margindata("flushing stage one, stored %s, scope %s, delayed %s, group %a",nofstored,scope,nofdelayed,group)
        end
        local current = head
        local done = false
        while current do
            local id = current.id
            if (id == vlist_code or id == hlist_code) and not current[a_margindata] then
                local don, continue = flushed(scope,current)
                if don then
                    current[a_margindata] = 0 -- signal to prevent duplicate processing
                    if continue then
                        markovershoot(current)
                    end
                    if nofstored <= 0 then
                        break
                    end
                    done = true
                end
            end
            current = current.next
        end
     -- if done then
        resetstacked() -- why doesn't done work ok here?
     -- end
        return head, done
    else
        return head, false
    end
end

function margins.localhandler(head,group) -- sometimes group is "" which is weird
    local inhibit = conditionals.inhibitmargindata
    if inhibit then
        if trace_margingroup then
            report_margindata("ignored 3, group %a, stored %s, inhibit %a",group,nofstored,inhibit)
        end
        return head, false
    elseif nofstored > 0 then
        return handler(v_local,head,group)
    else
        if trace_margingroup then
            report_margindata("ignored 4, group %a, stored %s, inhibit %a",group,nofstored,inhibit)
        end
        return head, false
    end
end

function margins.globalhandler(head,group) -- check group
    local inhibit = conditionals.inhibitmargindata
    if inhibit or nofstored == 0 then
        if trace_margingroup then
            report_margindata("ignored 1, group %a, stored %s, inhibit %a",group,nofstored,inhibit)
        end
        return head, false
    elseif group == "hmode_par" then
        return handler("global",head,group)
    elseif group == "vmode_par" then              -- experiment (for alignments)
        return handler("global",head,group)
     -- this needs checking as we then get quite some one liners to process and
     -- we cannot look ahead then:
    elseif group == "box" then                    -- experiment (for alignments)
        return handler("global",head,group)
    elseif group == "alignment" then              -- experiment (for alignments)
        return handler("global",head,group)
    else
        if trace_margingroup then
            report_margindata("ignored 2, group %a, stored %s, inhibit %a",group,nofstored,inhibit)
        end
        return head, false
    end
end

local function finalhandler(head)
    if nofdelayed > 0 then
        local current = head
        local done = false
        while current do
            local id = current.id
            if id == hlist_code then
                local a = current[a_margindata]
                if not a or a == 0 then
                    finalhandler(current.list)
                elseif realigned(current,a) then
                    done = true
                    if nofdelayed == 0 then
                        return head, true
                    end
                end
            elseif id == vlist_code then
                finalhandler(current.list)
            end
            current = current.next
        end
        return head, done
    else
        return head, false
    end
end

function margins.finalhandler(head)
    if nofdelayed > 0 then
     -- if trace_margindata then
     --     report_margindata("flushing stage two, instore: %s, delayed: %s",nofstored,nofdelayed)
     -- end
        return finalhandler(head)
    else
        return head, false
    end
end

-- Somehow the vbox builder (in combinations) gets pretty confused and decides to
-- go horizontal. So this needs more testing.

prependaction("finalizers",   "lists",       "typesetters.margins.localhandler")
--           ("vboxbuilders", "normalizers", "typesetters.margins.localhandler")
prependaction("mvlbuilders",  "normalizers", "typesetters.margins.globalhandler")
prependaction("shipouts",     "normalizers", "typesetters.margins.finalhandler")

disableaction("finalizers",   "typesetters.margins.localhandler")
--           ("vboxbuilders", "typesetters.margins.localhandler")
disableaction("mvlbuilders",  "typesetters.margins.globalhandler")
disableaction("shipouts",     "typesetters.margins.finalhandler")

enablelocal = function()
    enableaction("finalizers",   "typesetters.margins.localhandler")
 -- enableaction("vboxbuilders", "typesetters.margins.localhandler")
    enableaction("shipouts",     "typesetters.margins.finalhandler")
    enablelocal = nil
end

enableglobal = function()
    enableaction("mvlbuilders",  "typesetters.margins.globalhandler")
    enableaction("shipouts",     "typesetters.margins.finalhandler")
    enableglobal = nil
end

statistics.register("margin data", function()
    if nofsaved > 0 then
        return format("%s entries, %s pending",nofsaved,nofdelayed)
    else
        return nil
    end
end)
