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

--[[ldx--
<p>We save positional information in the main utility table. Not only
can we store much more information in <l n='lua'/> but it's also
more efficient.</p>
--ldx]]--

-- plus (extra) is obsolete but we will keep it for a while
--
-- maybe replace texsp by our own converter (stay at the lua end)
-- eventually mp will have large numbers so we can use sp there too
--
-- this is one of the first modules using scanners and we need to replace it by
-- implement and friends
--
-- we could have namespaces, like p, page, region, columnarea, textarea but then
-- we need virtual table accessors as well as have tag/id accessors ... we don't
-- save much here (at least not now)
--
-- This was the last module that got rid of directly setting scanners, with a little
-- performance degradation but not that noticeable.

local tostring, next, setmetatable, tonumber = tostring, next, setmetatable, tonumber
local sort = table.sort
local format, gmatch = string.format, string.gmatch
local lpegmatch = lpeg.match
local insert, remove = table.insert, table.remove
local allocate = utilities.storage.allocate

local report            = logs.reporter("positions")

local scanners          = tokens.scanners
local scanstring        = scanners.string
local scaninteger       = scanners.integer
local scandimen         = scanners.dimen

local implement         = interfaces.implement

local commands          = commands
local context           = context

local ctx_latelua       = context.latelua

local tex               = tex
local texgetcount       = tex.getcount
local texgetinteger     = tex.getintegervalue or tex.getcount
local texsetcount       = tex.setcount
local texget            = tex.get
local texsp             = tex.sp
----- texsp             = string.todimen -- because we cache this is much faster but no rounding

local setmetatableindex    = table.setmetatableindex
local setmetatablenewindex = table.setmetatablenewindex

local nuts              = nodes.nuts

local setlink           = nuts.setlink
local getlist           = nuts.getlist
local setlist           = nuts.setlist
local getbox            = nuts.getbox
local getid             = nuts.getid
local getwhd            = nuts.getwhd

local hlist_code        = nodes.nodecodes.hlist

local find_tail         = nuts.tail
----- hpack             = nuts.hpack

local new_latelua       = nuts.pool.latelua

local variables         = interfaces.variables
local v_text            = variables.text
local v_column          = variables.column

local pt                = number.dimenfactors.pt
local pts               = number.pts
local formatters        = string.formatters

local collected         = allocate()
local tobesaved         = allocate()

local jobpositions = {
    collected = collected,
    tobesaved = tobesaved,
}

job.positions = jobpositions

local default = { -- not r and paragraphs etc
    __index = {
        x   = 0,     -- x position baseline
        y   = 0,     -- y position baseline
        w   = 0,     -- width
        h   = 0,     -- height
        d   = 0,     -- depth
        p   = 0,     -- page
        n   = 0,     -- paragraph
        ls  = 0,     -- leftskip
        rs  = 0,     -- rightskip
        hi  = 0,     -- hangindent
        ha  = 0,     -- hangafter
        hs  = 0,     -- hsize
        pi  = 0,     -- parindent
        ps  = false, -- parshape
        dir = 0,
    }
}

local f_b_tag     = formatters["b:%s"]
local f_e_tag     = formatters["e:%s"]
local f_p_tag     = formatters["p:%s"]
local f_w_tag     = formatters["w:%s"]

local f_region    = formatters["region:%s"]

local f_tag_three = formatters["%s:%s:%s"]
local f_tag_two   = formatters["%s:%s"]

local nofregular  = 0
local nofspecial  = 0
local splitter    = lpeg.splitat(":",true)

local pagedata    = { }
local columndata  = setmetatableindex("table") -- per page
local freedata    = setmetatableindex("table") -- per page

local function initializer()
    tobesaved = jobpositions.tobesaved
    collected = jobpositions.collected
    for tag, data in next, collected do
        local prefix, rest = lpegmatch(splitter,tag)
        if prefix == "p" then
            nofregular = nofregular + 1
        elseif prefix == "page" then
            nofregular = nofregular + 1
            pagedata[tonumber(rest) or 0] = data
        elseif prefix == "free" then
            nofspecial = nofspecial + 1
            local t = freedata[data.p or 0]
            t[#t+1] = data
        elseif prefix == "columnarea" then
            columndata[data.p or 0][data.c or 0] = data
        end
        setmetatable(data,default)
    end
    --
    local pages = structures.pages.collected
    if pages then
        local last = nil
        for p=1,#pages do
            local region = "page:" .. p
            local data   = pagedata[p]
            local free   = freedata[p]
            if free then
                sort(free,function(a,b) return b.y < a.y end) -- order matters !
            end
            if data then
                last      = data
                last.free = free
            elseif last then
                local t = setmetatableindex({ free = free, p = p },last)
                if not collected[region] then
                    collected[region] = t
                else
                    -- something is wrong
                end
                pagedata[p] = t
            end
        end
    end
    jobpositions.pagedata   = pagedata
end

function jobpositions.used()
    return next(collected) -- we can safe it
end

function jobpositions.getfree(page)
    return freedata[page]
end

-- we can gain a little when we group positions but then we still have to
-- deal with regions and cells so we either end up with lots of extra small
-- tables pointing to them and/or assembling/disassembling so in the end
-- it makes no sense to do it (now) and still have such a mix
--
-- proof of concept code removed ... see archive

local function finalizer()
    -- We make the (possible extensive) shape lists sparse working
    -- from the end. We could also drop entries here that have l and
    -- r the same which saves testing later on.
    for k, v in next, tobesaved do
        local s = v.s
        if s then
            for p, data in next, s do
                local n = #data
                if n > 1 then
                    local ph = data[1][2]
                    local pd = data[1][3]
                    local xl = data[1][4]
                    local xr = data[1][5]
                    for i=2,n do
                        local di = data[i]
                        local h = di[2]
                        local d = di[3]
                        local l = di[4]
                        local r = di[5]
                        if r == xr then
                            di[5] = nil
                            if l == xl then
                                di[4] = nil
                                if d == pd then
                                    di[3] = nil
                                    if h == ph then
                                        di[2] = nil
                                    else
                                        ph = h
                                    end
                                else
                                    pd, ph = d, h
                                end
                            else
                                ph, pd, xl = h, d, l
                            end
                        else
                            ph, pd, xl, xr = h, d, l, r
                        end
                    end
                end
            end
        end
    end
end

job.register('job.positions.collected', tobesaved, initializer, finalizer)

local regions    = { }
local nofregions = 0
local region     = nil

local columns    = { }
local nofcolumns = 0
local column     = nil

local nofpages   = nil

-- beware ... we're not sparse here as lua will reserve slots for the nilled

local getpos, gethpos, getvpos, getrpos

function jobpositions.registerhandlers(t)
    getpos  = t and t.getpos  or function() return 0, 0 end
    getrpos = t and t.getrpos or function() return 0, 0, 0 end
    gethpos = t and t.gethpos or function() return 0 end
    getvpos = t and t.getvpos or function() return 0 end
end

function jobpositions.getpos () return getpos () end
function jobpositions.getrpos() return getrpos() end
function jobpositions.gethpos() return gethpos() end
function jobpositions.getvpos() return getvpos() end

-------- jobpositions.getcolumn() return column end

jobpositions.registerhandlers()

local function setall(name,p,x,y,w,h,d,extra)
    tobesaved[name] = {
        p = p,
        x = x ~= 0 and x or nil,
        y = y ~= 0 and y or nil,
        w = w ~= 0 and w or nil,
        h = h ~= 0 and h or nil,
        d = d ~= 0 and d or nil,
        e = extra ~= "" and extra or nil,
        r = region,
        c = column,
        r2l = texgetinteger("inlinelefttoright") == 1 and true or nil,
    }
end

local function enhance(data)
    if not data then
        return nil
    end
    if data.r == true then -- or ""
        data.r = region
    end
    if data.x == true then
        if data.y == true then
            local x, y = getpos()
            data.x = x ~= 0 and x or nil
            data.y = y ~= 0 and y or nil
        else
            local x = gethpos()
            data.x = x ~= 0 and x or nil
        end
    elseif data.y == true then
        local y = getvpos()
        data.y = y ~= 0 and y or nil
    end
    if data.p == true then
        data.p = texgetcount("realpageno") -- we should use a variable set in otr
    end
    if data.c == true then
        data.c = column
    end
    if data.w == 0 then
        data.w = nil
    end
    if data.h == 0 then
        data.h = nil
    end
    if data.d == 0 then
        data.d = nil
    end
    return data
end

-- analyze some files (with lots if margindata) and then when one key optionally
-- use that one instead of a table (so, a 3rd / 4th argument: key, e.g. "x")

local function set(name,index,value) -- ,key
    -- officially there should have been a settobesaved
    local data = enhance(value or {})
    if value then
        container = tobesaved[name]
        if not container then
            tobesaved[name] = {
                [index] = data
            }
        else
            container[index] = data
        end
    else
        tobesaved[name] = data
    end
end

local function setspec(specification)
    local name  = specification.name
    local index = specification.index
    local value = specification.value
    local data  = enhance(value or {})
    if value then
        container = tobesaved[name]
        if not container then
            tobesaved[name] = {
                [index] = data
            }
        else
            container[index] = data
        end
    else
        tobesaved[name] = data
    end
end

local function get(id,index)
    if index then
        local container = collected[id]
        return container and container[index]
    else
        return collected[id]
    end
end

------------.setdim  = setdim
jobpositions.setall  = setall
jobpositions.set     = set
jobpositions.setspec = setspec
jobpositions.get     = get

implement {
    name      = "dosaveposition",
    arguments = { "string", "integer", "dimen", "dimen" },
    actions   = setall, -- name p x y
}

implement {
    name      = "dosavepositionwhd",
    arguments = { "string", "integer", "dimen", "dimen", "dimen", "dimen", "dimen" },
    actions   = setall, -- name p x y w h d
}

implement {
    name     = "dosavepositionplus",
    arguments = { "string", "integer", "dimen", "dimen", "dimen", "dimen", "dimen", "string" },
    actions   = setall,  -- name p x y w h d extra
}

-- will become private table (could also become attribute driven but too nasty
-- as attributes can bleed e.g. in margin stuff)

-- not much gain in keeping stack (inc/dec instead of insert/remove)

local function b_column(specification)
    local tag = specification.tag
    local x = gethpos()
    tobesaved[tag] = {
        r = true,
        x = x ~= 0 and x or nil,
     -- w = 0,
    }
    insert(columns,tag)
    column = tag
end

local function e_column()
    local t = tobesaved[column]
    if not t then
        -- something's wrong
    else
        local x = gethpos() - t.x
        t.w = x ~= 0 and x or nil
        t.r = region
    end
    remove(columns)
    column = columns[#columns]
end

jobpositions.b_column = b_column
jobpositions.e_column = e_column

implement {
    name      = "bposcolumn",
    arguments = "string",
    actions   = function(tag)
        insert(columns,tag)
        column = tag
    end
}

implement {
    name      = "bposcolumnregistered",
    arguments = "string",
    actions   = function(tag)
        insert(columns,tag)
        column = tag
        ctx_latelua { action = b_column, tag = tag }
    end
}

implement {
    name    = "eposcolumn",
    actions = function()
        remove(columns)
        column = columns[#columns]
    end
}

implement {
    name    = "eposcolumnregistered",
    actions = function()
        ctx_latelua { action = e_column }
        remove(columns)
        column = columns[#columns]
    end
}

-- regions

local function b_region(specification)
    local tag  = specification.tag or specification
    local last = tobesaved[tag]
    local x, y = getpos()
    last.x = x ~= 0 and x or nil
    last.y = y ~= 0 and y or nil
    last.p = texgetcount("realpageno")
    insert(regions,tag) -- todo: fast stack
    region = tag
end

local function e_region(specification)
    local last = tobesaved[region]
    local y = getvpos()
    local x, y = getpos()
    if specification.correct then
        local h = (last.y or 0) - y
        last.h = h ~= 0 and h or nil
    end
    last.y = y ~= 0 and y or nil
    remove(regions) -- todo: fast stack
    region = regions[#regions]
end

jobpositions.b_region = b_region
jobpositions.e_region = e_region

local lastregion

local function setregionbox(n,tag,k,lo,ro,to,bo,column) -- kind
    if not tag or tag == "" then
        nofregions = nofregions + 1
        tag = f_region(nofregions)
    end
    local box = getbox(n)
    local w, h, d = getwhd(box)
    tobesaved[tag] = {
     -- p  = texgetcount("realpageno"), -- we copy them
        x  = 0,
        y  = 0,
        w  = w  ~= 0 and w  or nil,
        h  = h  ~= 0 and h  or nil,
        d  = d  ~= 0 and d  or nil,
        k  = k  ~= 0 and k  or nil,
        lo = lo ~= 0 and lo or nil,
        ro = ro ~= 0 and ro or nil,
        to = to ~= 0 and to or nil,
        bo = bo ~= 0 and bo or nil,
        c  = column         or nil,
    }
    lastregion = tag
    return tag, box
end

local function markregionbox(n,tag,correct,...) -- correct needs checking
    local tag, box = setregionbox(n,tag,...)
     -- todo: check if tostring is needed with formatter
    local push = new_latelua { action = b_region, tag = tag }
    local pop  = new_latelua { action = e_region, correct = correct }
    -- maybe we should construct a hbox first (needs experimenting) so that we can avoid some at the tex end
    local head = getlist(box)
    -- no, this fails with \framed[region=...] .. needs thinking
 -- if getid(box) ~= hlist_code then
 --  -- report("mark region box assumes a hlist, fix this for %a",tag)
 --     head = hpack(head)
 -- end
    if head then
        local tail = find_tail(head)
        setlink(push,head)
        setlink(tail,pop)
    else -- we can have a simple push/pop
        setlink(push,pop)
    end
    setlist(box,push)
end

jobpositions.markregionbox = markregionbox
jobpositions.setregionbox  = setregionbox

function jobpositions.enhance(name)
    enhance(tobesaved[name])
end

function jobpositions.gettobesaved(name,tag)
    local t = tobesaved[name]
    if t and tag then
        return t[tag]
    else
        return t
    end
end

function jobpositions.settobesaved(name,tag,data)
    local t = tobesaved[name]
    if t and tag and data then
        t[tag] = data
    end
end

local nofparagraphs = 0

implement {
    name    = "parpos",
    actions = function()
        nofparagraphs = nofparagraphs + 1
        texsetcount("global","c_anch_positions_paragraph",nofparagraphs)
        local box = getbox("strutbox")
        local w, h, d = getwhd(box)
        local t = {
            p  = true,
            c  = true,
            r  = true,
            x  = true,
            y  = true,
            h  = h,
            d  = d,
            hs = texget("hsize"), -- never 0
        }
        local leftskip   = texget("leftskip",false)
        local rightskip  = texget("rightskip",false)
        local hangindent = texget("hangindent")
        local hangafter  = texget("hangafter")
        local parindent  = texget("parindent")
        local parshape   = texget("parshape")
        if leftskip ~= 0 then
            t.ls = leftskip
        end
        if rightskip ~= 0 then
            t.rs = rightskip
        end
        if hangindent ~= 0 then
            t.hi = hangindent
        end
        if hangafter ~= 1 and hangafter ~= 0 then -- can not be zero .. so it needs to be 1 if zero
            t.ha = hangafter
        end
        if parindent ~= 0 then
            t.pi = parindent
        end
        if parshape and #parshape > 0 then
            t.ps = parshape
        end
        local name = f_p_tag(nofparagraphs)
        tobesaved[name] = t
        ctx_latelua { action = enhance, specification = t }
    end
}

implement {
    name      = "dosetposition",
    arguments = "string",
    actions   = function(name)
        local spec = {
            p   = true,
            c   = column,
            r   = true,
            x   = true,
            y   = true,
            n   = nofparagraphs > 0 and nofparagraphs or nil,
            r2l = texgetinteger("inlinelefttoright") == 1 or nil,
        }
        tobesaved[name] = spec
        ctx_latelua { action = enhance, specification = spec }
    end
}

implement {
    name      = "dosetpositionwhd",
    arguments = { "string", "dimen", "dimen", "dimen" },
    actions   = function(name,w,h,d)
        local spec = {
            p   = true,
            c   = column,
            r   = true,
            x   = true,
            y   = true,
            w   = w ~= 0 and w or nil,
            h   = h ~= 0 and h or nil,
            d   = d ~= 0 and d or nil,
            n   = nofparagraphs > 0 and nofparagraphs or nil,
            r2l = texgetinteger("inlinelefttoright") == 1 or nil,
        }
        tobesaved[name] = spec
        ctx_latelua { action = enhance, specification = spec }
    end
}

implement {
    name      = "dosetpositionbox",
    arguments = { "string", "integer" },
    actions   = function(name,n)
        local box  = getbox(n)
        local w, h, d = getwhd(box)
        local spec = {
            p = true,
            c = column,
            r = true,
            x = true,
            y = true,
            w = w ~= 0 and w or nil,
            h = h ~= 0 and h or nil,
            d = d ~= 0 and d or nil,
            n = nofparagraphs > 0 and nofparagraphs or nil,
            r2l = texgetinteger("inlinelefttoright") == 1 or nil,
        }
        tobesaved[name] = spec
        ctx_latelua { action = enhance, specification = spec }
    end
}

implement {
    name      = "dosetpositionplus",
    arguments = { "string", "dimen", "dimen", "dimen" },
    actions   = function(name,w,h,d)
        local spec = {
            p   = true,
            c   = column,
            r   = true,
            x   = true,
            y   = true,
            w   = w ~= 0 and w or nil,
            h   = h ~= 0 and h or nil,
            d   = d ~= 0 and d or nil,
            n   = nofparagraphs > 0 and nofparagraphs or nil,
            e   = scanstring(),
            r2l = texgetinteger("inlinelefttoright") == 1 or nil,
        }
        tobesaved[name] = spec
        ctx_latelua { action = enhance, specification = spec }
    end
}

implement {
    name      = "dosetpositionstrut",
    arguments = "string",
    actions   = function(name)
        local box = getbox("strutbox")
        local w, h, d = getwhd(box)
        local spec = {
            p   = true,
            c   = column,
            r   = true,
            x   = true,
            y   = true,
            h   = h ~= 0 and h or nil,
            d   = d ~= 0 and d or nil,
            n   = nofparagraphs > 0 and nofparagraphs or nil,
            r2l = texgetinteger("inlinelefttoright") == 1 or nil,
        }
        tobesaved[name] = spec
        ctx_latelua { action = enhance, specification = spec }
    end
}

implement {
    name      = "dosetpositionstrutkind",
    arguments = { "string", "integer" },
    actions   = function(name,kind)
        local box = getbox("strutbox")
        local w, h, d = getwhd(box)
        local spec = {
            k   = kind,
            p   = true,
            c   = column,
            r   = true,
            x   = true,
            y   = true,
            h   = h ~= 0 and h or nil,
            d   = d ~= 0 and d or nil,
            n   = nofparagraphs > 0 and nofparagraphs or nil,
            r2l = texgetinteger("inlinelefttoright") == 1 or nil,
        }
        tobesaved[name] = spec
        ctx_latelua { action = enhance, specification = spec }
    end
}

function jobpositions.getreserved(tag,n)
    if tag == v_column then
        local fulltag = f_tag_three(tag,texgetcount("realpageno"),n or 1)
        local data = collected[fulltag]
        if data then
            return data, fulltag
        end
        tag = v_text
    end
    if tag == v_text then
        local fulltag = f_tag_two(tag,texgetcount("realpageno"))
        return collected[fulltag] or false, fulltag
    end
    return collected[tag] or false, tag
end

function jobpositions.copy(target,source)
    collected[target] = collected[source]
end

function jobpositions.replace(id,p,x,y,w,h,d)
    collected[id] = { p = p, x = x, y = y, w = w, h = h, d = d } -- c g
end

local function getpage(id)
    local jpi = collected[id]
    return jpi and jpi.p
end

local function getcolumn(id)
    local jpi = collected[id]
    return jpi and jpi.c or false
end

local function getparagraph(id)
    local jpi = collected[id]
    return jpi and jpi.n
end

local function getregion(id)
    local jpi = collected[id]
    if jpi then
        local r = jpi.r
        if r then
            return r
        end
        local p = jpi.p
        if p then
            return "page:" .. p
        end
    end
    return false
end

jobpositions.page      = getpage
jobpositions.column    = getcolumn
jobpositions.paragraph = getparagraph
jobpositions.region    = getregion

jobpositions.p = getpage      -- not used, kind of obsolete
jobpositions.c = getcolumn    -- idem
jobpositions.n = getparagraph -- idem
jobpositions.r = getregion    -- idem

function jobpositions.x(id)
    local jpi = collected[id]
    return jpi and jpi.x
end

function jobpositions.y(id)
    local jpi = collected[id]
    return jpi and jpi.y
end

function jobpositions.width(id)
    local jpi = collected[id]
    return jpi and jpi.w
end

function jobpositions.height(id)
    local jpi = collected[id]
    return jpi and jpi.h
end

function jobpositions.depth(id)
    local jpi = collected[id]
    return jpi and jpi.d
end

function jobpositions.whd(id)
    local jpi = collected[id]
    if jpi then
        return jpi.h, jpi.h, jpi.d
    end
end

function jobpositions.leftskip(id)
    local jpi = collected[id]
    return jpi and jpi.ls
end

function jobpositions.rightskip(id)
    local jpi = collected[id]
    return jpi and jpi.rs
end

function jobpositions.hsize(id)
    local jpi = collected[id]
    return jpi and jpi.hs
end

function jobpositions.parindent(id)
    local jpi = collected[id]
    return jpi and jpi.pi
end

function jobpositions.hangindent(id)
    local jpi = collected[id]
    return jpi and jpi.hi
end

function jobpositions.hangafter(id)
    local jpi = collected[id]
    return jpi and jpi.ha or 1
end

function jobpositions.xy(id)
    local jpi = collected[id]
    if jpi then
        return jpi.x, jpi.y
    else
        return 0, 0
    end
end

function jobpositions.lowerleft(id)
    local jpi = collected[id]
    if jpi then
        return jpi.x, jpi.y - jpi.d
    else
        return 0, 0
    end
end

function jobpositions.lowerright(id)
    local jpi = collected[id]
    if jpi then
        return jpi.x + jpi.w, jpi.y - jpi.d
    else
        return 0, 0
    end
end

function jobpositions.upperright(id)
    local jpi = collected[id]
    if jpi then
        return jpi.x + jpi.w, jpi.y + jpi.h
    else
        return 0, 0
    end
end

function jobpositions.upperleft(id)
    local jpi = collected[id]
    if jpi then
        return jpi.x, jpi.y + jpi.h
    else
        return 0, 0
    end
end

function jobpositions.position(id)
    local jpi = collected[id]
    if jpi then
        return jpi.p, jpi.x, jpi.y, jpi.w, jpi.h, jpi.d
    else
        return 0, 0, 0, 0, 0, 0
    end
end

local splitter = lpeg.splitat(",")

function jobpositions.extra(id,n,default) -- assume numbers
    local jpi = collected[id]
    if jpi then
        local e = jpi.e
        if e then
            local split = jpi.split
            if not split then
                split = lpegmatch(splitter,jpi.e)
                jpi.split = split
            end
            return texsp(split[n]) or default -- watch the texsp here
        end
    end
    return default
end

local function overlapping(one,two,overlappingmargin) -- hm, strings so this is wrong .. texsp
    one = collected[one]
    two = collected[two]
    if one and two and one.p == two.p then
        if not overlappingmargin then
            overlappingmargin = 2
        end
        local x_one = one.x
        local x_two = two.x
        local w_two = two.w
        local llx_one = x_one         - overlappingmargin
        local urx_two = x_two + w_two + overlappingmargin
        if llx_one > urx_two then
            return false
        end
        local w_one = one.w
        local urx_one = x_one + w_one + overlappingmargin
        local llx_two = x_two         - overlappingmargin
        if urx_one < llx_two then
            return false
        end
        local y_one = one.y
        local y_two = two.y
        local d_one = one.d
        local h_two = two.h
        local lly_one = y_one - d_one - overlappingmargin
        local ury_two = y_two + h_two + overlappingmargin
        if lly_one > ury_two then
            return false
        end
        local h_one = one.h
        local d_two = two.d
        local ury_one = y_one + h_one + overlappingmargin
        local lly_two = y_two - d_two - overlappingmargin
        if ury_one < lly_two then
            return false
        end
        return true
    end
end

local function onsamepage(list,page)
    for id in gmatch(list,"(, )") do
        local jpi = collected[id]
        if jpi then
            local p = jpi.p
            if not p then
                return false
            elseif not page then
                page = p
            elseif page ~= p then
                return false
            end
        end
    end
    return page
end

local function columnofpos(realpage,xposition)
    local p = columndata[realpage]
    if p then
        for i=1,#p do
            local c = p[i]
            local x = c.x or 0
            local w = c.w or 0
            if xposition >= x and xposition <= (x + w) then
                return i
            end
        end
    end
    return 1
end

jobpositions.overlapping = overlapping
jobpositions.onsamepage  = onsamepage
jobpositions.columnofpos = columnofpos

-- interface

implement {
    name      = "replacepospxywhd",
    arguments = { "string", "integer", "dimen", "dimen", "dimen", "dimen", "dimen" },
    actions   = function(name,page,x,y,w,h,d)
        collected[name] = {
            p = page,
            x = x,
            y = y,
            w = w,
            h = h,
            d = d,
        }
    end
}

implement {
    name      = "copyposition",
    arguments = "2 strings",
    actions   = function(target,source)
        collected[target] = collected[source]
    end
}

implement {
    name      = "MPp",
    arguments = "string",
    actions   = function(name)
        local jpi = collected[name]
        if jpi then
            local p = jpi.p
            if p and p ~= true then
                context(p)
                return
            end
        end
        context('0')
    end
}

implement {
    name      = "MPx",
    arguments = "string",
    actions   = function(name)
        local jpi = collected[name]
        if jpi then
            local x = jpi.x
            if x and x ~= true and x ~= 0 then
                context("%.5Fpt",x*pt)
                return
            end
        end
        context('0pt')
    end
}

implement {
    name      = "MPy",
    arguments = "string",
    actions   = function(name)
        local jpi = collected[name]
        if jpi then
            local y = jpi.y
            if y and y ~= true and y ~= 0 then
                context("%.5Fpt",y*pt)
                return
            end
        end
        context('0pt')
    end
}

implement {
    name      = "MPw",
    arguments = "string",
    actions   = function(name)
        local jpi = collected[name]
        if jpi then
            local w = jpi.w
            if w and w ~= 0 then
                context("%.5Fpt",w*pt)
                return
            end
        end
        context('0pt')
    end
}

implement {
    name      = "MPh",
    arguments = "string",
    actions   = function(name)
        local jpi = collected[name]
        if jpi then
            local h = jpi.h
            if h and h ~= 0 then
                context("%.5Fpt",h*pt)
                return
            end
        end
        context('0pt')
    end
}

implement {
    name      = "MPd",
    arguments = "string",
    actions   = function(name)
        local jpi = collected[name]
        if jpi then
            local d = jpi.d
            if d and d ~= 0 then
                context("%.5Fpt",d*pt)
                return
            end
        end
        context('0pt')
    end
}

implement {
    name      = "MPxy",
    arguments = "string",
    actions   = function(name)
        local jpi = collected[name]
        if jpi then
            context('(%.5Fpt,%.5Fpt)',
                jpi.x*pt,
                jpi.y*pt
            )
        else
            context('(0,0)')
        end
    end
}

implement {
    name      = "MPwhd",
    arguments = "string",
    actions   = function(name)
        local jpi = collected[name]
        if jpi then
            local w = jpi.w or 0
            local h = jpi.h or 0
            local d = jpi.d or 0
            if w ~= 0 or h ~= 0 or d ~= 0 then
                context("%.5Fpt,%.5Fpt,%.5Fpt",w*pt,h*pt,d*pt)
                return
            end
        end
        context('0pt,0pt,0pt')
    end
}

implement {
    name      = "MPll",
    arguments = "string",
    actions   = function(name)
        local jpi = collected[name]
        if jpi then
            context('(%.5Fpt,%.5Fpt)',
                 jpi.x       *pt,
                (jpi.y-jpi.d)*pt
            )
        else
            context('(0,0)') -- for mp only
        end
    end
}

implement {
    name      = "MPlr",
    arguments = "string",
    actions   = function(name)
        local jpi = collected[name]
        if jpi then
            context('(%.5Fpt,%.5Fpt)',
                (jpi.x + jpi.w)*pt,
                (jpi.y - jpi.d)*pt
            )
        else
            context('(0,0)') -- for mp only
        end
    end
}

implement {
    name      = "MPur",
    arguments = "string",
    actions   = function(name)
        local jpi = collected[name]
        if jpi then
            context('(%.5Fpt,%.5Fpt)',
                (jpi.x + jpi.w)*pt,
                (jpi.y + jpi.h)*pt
            )
        else
            context('(0,0)') -- for mp only
        end
    end
}

implement {
    name      = "MPul",
    arguments = "string",
    actions   = function(name)
        local jpi = collected[name]
        if jpi then
            context('(%.5Fpt,%.5Fpt)',
                 jpi.x         *pt,
                (jpi.y + jpi.h)*pt
            )
        else
            context('(0,0)') -- for mp only
        end
    end
}

local function MPpos(id)
    local jpi = collected[id]
    if jpi then
        local p = jpi.p
        if p then
            context("%s,%.5Fpt,%.5Fpt,%.5Fpt,%.5Fpt,%.5Fpt",
                p,
                jpi.x*pt,
                jpi.y*pt,
                jpi.w*pt,
                jpi.h*pt,
                jpi.d*pt
            )
            return
        end
    end
    context('0,0,0,0,0,0') -- for mp only
end

implement {
    name      = "MPpos",
    arguments = "string",
    actions   = MPpos
}

implement {
    name      = "MPn",
    arguments = "string",
    actions   = function(name)
        local jpi = collected[name]
        if jpi then
            local n = jpi.n
            if n then
                context(n)
                return
            end
        end
        context(0)
    end
}

implement {
    name      = "MPc",
    arguments = "string",
    actions   = function(name)
        local jpi = collected[name]
        if jpi then
            local c = jpi.c
            if c and c ~= true  then
                context(c)
                return
            end
        end
        context('0') -- okay ?
    end
}

implement {
    name      = "MPr",
    arguments = "string",
    actions   = function(name)
        local jpi = collected[name]
        if jpi then
            local r = jpi.r
            if r and r ~= true  then
                context(r)
                return
            end
            local p = jpi.p
            if p and p ~= true then
                context("page:" .. p)
            end
        end
    end
}

local function MPpardata(id)
    local t = collected[id]
    if not t then
        local tag = f_p_tag(id)
        t = collected[tag]
    end
    if t then
        context("%.5Fpt,%.5Fpt,%.5Fpt,%.5Fpt,%s,%.5Fpt",
            t.hs*pt,
            t.ls*pt,
            t.rs*pt,
            t.hi*pt,
            t.ha,
            t.pi*pt
        )
    else
        context("0,0,0,0,0,0") -- for mp only
    end
end

implement {
    name      = "MPpardata",
    arguments = "string",
    actions   = MPpardata
}

implement {
    name      = "MPposset",
    arguments = "string",
    actions   = function(name)
        local b = f_b_tag(name)
        local e = f_e_tag(name)
        local w = f_w_tag(name)
        local p = f_p_tag(getparagraph(b))
        MPpos(b) context(",") MPpos(e) context(",") MPpos(w) context(",") MPpos(p) context(",") MPpardata(p)
    end
}

implement {
    name      = "MPls",
    arguments = "string",
    actions   = function(name)
        local jpi = collected[name]
        if jpi then
            context("%.5Fpt",jpi.ls*pt)
        else
            context("0pt")
        end
    end
}

implement {
    name      = "MPrs",
    arguments = "string",
    actions   = function(name)
        local jpi = collected[name]
        if jpi then
            context("%.5Fpt",jpi.rs*pt)
        else
            context("0pt")
        end
    end
}

local splitter = lpeg.tsplitat(",")

implement {
    name      = "MPplus",
    arguments = { "string", "integer", "string" },
    actions   = function(name,n,default)
        local jpi = collected[name]
        if jpi then
            local e = jpi.e
            if e then
                local split = jpi.split
                if not split then
                    split = lpegmatch(splitter,jpi.e)
                    jpi.split = split
                end
                context(split[n] or default)
                return
            end
        end
        context(default)
    end
}

implement {
    name      = "MPrest",
    arguments = { "string", "string" },
    actions   = function(name,default)
        local jpi = collected[name]
        context(jpi and jpi.e or default)
    end
}

implement {
    name      = "MPxywhd",
    arguments = "string",
    actions   = function(name)
        local jpi = collected[name]
        if jpi then
            context("%.5Fpt,%.5Fpt,%.5Fpt,%.5Fpt,%.5Fpt",
                jpi.x*pt,
                jpi.y*pt,
                jpi.w*pt,
                jpi.h*pt,
                jpi.d*pt
            )
        else
            context("0,0,0,0,0") -- for mp only
        end
    end
}

local doif     = commands.doif
local doifelse = commands.doifelse

implement {
    name      = "doifelseposition",
    arguments = "string",
    actions   = function(name)
        doifelse(collected[name])
    end
}

implement {
    name      = "doifposition",
    arguments = "string",
    actions   = function(name)
        doif(collected[name])
    end
}

implement {
    name      = "doifelsepositiononpage",
    arguments = { "string", "integer" },
    actions   = function(name,p)
        local c = collected[name]
        doifelse(c and c.p == p)
    end
}

implement {
    name      = "doifelseoverlapping",
    arguments = { "string", "string" },
    actions   = function(one,two)
        doifelse(overlapping(one,two))
    end
}

implement {
    name      = "doifelsepositionsonsamepage",
    arguments = "string",
    actions   = function(list)
        doifelse(onsamepage(list))
    end
}

implement {
    name      = "doifelsepositionsonthispage",
    arguments = "string",
    actions   = function(list)
        doifelse(onsamepage(list,tostring(texgetcount("realpageno"))))
    end
}

implement {
    name      = "doifelsepositionsused",
    actions   = function()
        doifelse(next(collected))
    end
}

implement {
    name      = "markregionbox",
    arguments = "integer",
    actions   = markregionbox
}

implement {
    name      = "setregionbox",
    arguments = "integer",
    actions   = setregionbox
}

implement {
    name      = "markregionboxtagged",
    arguments = { "integer", "string" },
    actions   = markregionbox
}

implement {
    name      = "markregionboxtaggedn",
    arguments = { "integer", "string", "integer" },
    actions   = function(box,tag,n)
        markregionbox(box,tag,nil,nil,nil,nil,nil,nil,n)
    end
}

implement {
    name      = "setregionboxtagged",
    arguments = { "integer", "string" },
    actions   = setregionbox
}

implement {
    name      = "markregionboxcorrected",
    arguments = { "integer", "string", true },
    actions   = markregionbox
}

implement {
    name      = "markregionboxtaggedkind",
    arguments = { "integer", "string", "integer", "dimen", "dimen", "dimen", "dimen" },
    actions   = function(box,tag,n,d1,d2,d3,d4)
        markregionbox(box,tag,nil,n,d1,d2,d3,d4)
    end
}

implement {
    name    = "reservedautoregiontag",
    actions = function()
        nofregions = nofregions + 1
        context(f_region(nofregions))
    end
}

-- statistics (at least for the moment, when testing)

-- statistics.register("positions", function()
--     local total = nofregular + nofusedregions + nofmissingregions
--     if total > 0 then
--         return format("%s collected, %s regulars, %s regions, %s unresolved regions",
--             total, nofregular, nofusedregions, nofmissingregions)
--     else
--         return nil
--     end
-- end)

statistics.register("positions", function()
    local total = nofregular + nofspecial
    if total > 0 then
        return format("%s collected, %s regular, %s special",total,nofregular,nofspecial)
    else
        return nil
    end
end)

-- We support the low level positional commands too:

local newsavepos = nodes.pool.savepos

implement { name = "savepos",  actions = function() context(newsavepos()) end }
implement { name = "lastxpos", actions = function() context(gethpos()) end }
implement { name = "lastypos", actions = function() context(getvpos()) end }
