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"
}

-- We save positional information in the main utility table. Not only can we store
-- much more information in Lua but it's also more efficient. In the meantime these
-- files have become quite large. In some cases that get noticed by a hickup in the
-- start and/or finish, but that is the price we pay for progress.
--
-- This was the last module that got rid of directly setting scanners, with a little
-- performance degradation but not that noticeable. It is also a module that has been
-- on the (partial) redo list for a while.
--
-- 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. I played with that and rejected the idea
-- until I ran into a test case where we had 50.000 one line paragraphs in an eight
-- columns setup, and there we save 25 M on a 75 M tuc file. So, I played a bit more
-- and we can have a solution that is of similar performance for regular documents
-- (in spite of the extra overhead) but also works ok for the large files. In normal
-- documents it is never a problem, but there are always exceptions to the normal and
-- often these are also cases where positions are not really used but end up in the
-- tuc file anyway.
--
-- Currently (because we never had split tags) we do splitting at access time, which
-- is sort of inefficient but still ok. Much of this mechanism comes from MkII where
-- TeX was the bottleneck.
--
-- By grouping we need to set more metatable so in the end that is also overhead
-- but on the average we're okay because excessive serialization also comes at a price
-- and this way we also delay some activities till the moment it is realy used (which
-- is not always the case with positional information. We will make this transition
-- stepwise so till we're done there will be inefficiencies and overhead.
--
-- The pure hash based variant combined with filtering is in anch-pos.lua and previous
-- lmt versions! That is the reference.

local tostring, next, setmetatable, tonumber, rawget, rawset = tostring, next, setmetatable, tonumber, rawget, rawset
local sort, sortedhash = table.sort, table.sortedhash
local format, gmatch = string.format, string.gmatch
local P, R, C, Cc, lpegmatch = lpeg.P, lpeg.R, lpeg.C, lpeg.Cc, lpeg.match
local insert, remove = table.insert, table.remove
local allocate = utilities.storage.allocate
local setmetatableindex, setmetatablenewindex = table.setmetatableindex, table.setmetatablenewindex
local fromposit = posit.fromposit

local report            = logs.reporter("positions")

local scanstring        = tokens.scanners.string

local implement         = interfaces.implement

local commands          = commands
local context           = context

local ctx_latelua       = context.latelua

local ctx_doif          = commands.doif
local ctx_doifelse      = commands.doifelse

local tex               = tex
local texgetdimen       = tex.getdimen
local texgetcount       = tex.getcount
local texgetinteger     = tex.getintegervalue or tex.getcount
local texiscount        = tex.iscount
local texisdimen        = tex.isdimen
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 texgetnest        = tex.getnest
local texgetparstate    = tex.getparstate

local nuts              = nodes.nuts
local tonut             = nodes.tonut

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 setprop           = nuts.setprop

local getparstate       = nuts.getparstate

local hlist_code        <const> = nodes.nodecodes.hlist
local par_code          <const> = nodes.nodecodes.par

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

local new_latelua       = nuts.pool.latelua

local v_text            <const> = interfaces.variables.text
local v_column          <const> = interfaces.variables.column

local pt                <const> = number.dimenfactors.pt

local formatters        = string.formatters

local collected         = allocate()
local tobesaved         = allocate()
local positionsused     = nil

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

job.positions = jobpositions

local dimension_code <const> = tokens.values.dimension
local integer_code   <const> = tokens.values.integer

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,     -- obsolete
        r2l = false, -- righttoleft
        dir = false,
    }
}

local f_b_tag      = formatters["b:%s"]
local f_e_tag      = formatters["e:%s"]
local f_p_tag      = formatters["p:%s"]
----- 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 c_realpageno        <const> = texiscount("realpageno")
local d_strutht           <const> = texisdimen("strutht")
local d_strutdp           <const> = texisdimen("strutdp")
local c_inlinelefttoright <const> = texiscount("inlinelefttoright")

-- Because positions are set with a delay we cannot yet make the tree -- so that
-- is a finalizer step. But, we already have a dual split.

local treemode = false
local treemode = true

local function checkshapes(s)
    for p, data in next, s do
        local n = #data
        if n > 1 then
            local d1 = data[1]
            local ph = d1[2]
            local pd = d1[3]
            local xl = d1[4]
            local xr = d1[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

local columndata = { }
local freedata   = { }       -- we can make these weak
local syncdata   = { }       -- we can make these weak
local columndone = false

if treemode then

    -- At some point we can install extra ones. I actually was halfway making a more
    -- general installer but we have quite some distinct handling down here and it
    -- became messy. So I rolled that back. Also, users and modules will quite likely
    -- stay in the "user" namespace.

    -- syncpos    : indirect access via helper, todo after we switch: direct setters
    -- free       : indirect access via helper, todo after we switch: direct setters
    -- columnarea : indirect access via helper, todo after we switch: direct setters

    -- todo: keep track of total and check that against # (sanity check)

    local prefix_number       = { "text", "textarea", "page", "p", "free", "columnarea" }
    local prefix_label_number = { "syncpos" }
    local prefix_number_rest  = { "region", "b", "e" }

    -- no need to split: syncpos free columnarea (textarea?)

    local function splitter_pattern()
        local p_number = R("09")^1/tonumber
        local p_colon  = P(":")
        local p_label  = C(P(1 - p_colon)^0)
        local p_rest   = C(P(1)^0)
        return
            C(lpeg.utfchartabletopattern(prefix_number      )) * p_colon * p_number * P(-1)
          + C(lpeg.utfchartabletopattern(prefix_label_number)) * p_colon * (p_number + p_label) * p_colon * p_number * P(-1)
          + C(lpeg.utfchartabletopattern(prefix_number_rest )) * p_colon * (p_number + p_rest)
          + Cc("user")                                         * p_rest
    end

    -- In the end these metatable entries are not more efficient than copying
    -- but it's all about making sure that the tuc file doesn't explode.

    columndata = { }
    columndone = false

    local deltapacking = true -- so we can see the difference
--     local deltapacking = false -- so we can see the difference

    local function checkcommondata(v,common)
        if common then
            local i = v.i
            local t = common[i]
            if t then
v.i = nil
                local m = t.mt
                if not m then
                    setmetatable(t,default)
                    m = { __index = t }
                    t.mt = m
                end
                setmetatable(v,m)
                return
            end
        end
        setmetatable(v,default)
    end

    local function initializer()
        tobesaved = jobpositions.tobesaved
        collected = jobpositions.collected
        --
        local p_splitter = splitter_pattern()
        --
        local list = nil
        --
        local shared        = setmetatableindex(rawget(collected,"shared"),"table")
        local x_y_w_h_list  = shared.x_y_w_h
        local y_w_h_d_list  = shared.y_w_h_d
        local x_h_d_list    = shared.x_h_d
        local x_h_d_hs_list = shared.x_h_d_hs
        --
        columndata = setmetatableindex(function(t,k)
            setmetatableindex(t,"table")
            list = rawget(collected,"columnarea")
            if list then
             -- for tag, data in next, list do
                for i=1,#list do
                    local data = list[i]
                    columndata[data.p or 0][data.c or 0] = data
                    checkcommondata(data,y_w_h_d_list)
                end
            end
            columndone = true
            return t[k]
        end)
        --
        -- todo: use a raw collected and a weak proxy
        --
        setmetatableindex(collected,function(t,k)
            if k ~= true then
                local prefix, one, two = lpegmatch(p_splitter,k)
                local list = rawget(t,prefix)
                if list and type(list) == "table" then
                    local v = list[one] or false
                    if v then
                        if prefix == "p" then
                         -- if deltapacking and type(v) == "number" then
                            if type(v) == "number" then
                                for i=one,1,-1 do
                                    local l = list[i]
                                    if type(l) ~= "number" then
                                        if not getmetatable(l) then
                                            checkcommondata(l,x_h_d_hs_list)
                                        end
                                        v = setmetatable({ y = v }, { __index = l })
                                        list[one] = v
                                        break
                                    end
                                end
                            else
                                checkcommondata(v,x_h_d_hs_list)
                            end
                        elseif prefix == "text" or prefix == "textarea" then
                            if type(v) == "number" then
                                for i=one,1,-1 do
                                    local l = list[i]
                                    if type(l) ~= "number" then
                                        if not getmetatable(l) then
                                            checkcommondata(l,x_y_w_h_list)
                                        end
                                        v = setmetatable({ p = v }, { __index = l })
                                        list[one] = v
                                        break
                                    end
                                end
                            else
                                checkcommondata(v,x_y_w_h_list)
                            end
                        elseif prefix == "columnarea" then
                            if not columndone then
                                checkcommondata(v,y_w_h_d_list)
                            end
                        elseif prefix == "syncpos" then
                            -- will become an error
                            if two then
                             -- v = syncdata[one][two] or { }
                                v = v[two] or { }
                            else
                                v = { }
                            end
                         -- for j=1,#v do
                         --     checkcommondata(v[j],x_h_d_list)
                         -- end
                        elseif prefix == "free" then
                            -- will become an error
                        elseif prefix == "page" then
                            checkcommondata(v)
                        else
                            checkcommondata(v)
                        end
                    else
                        if prefix == "page" then
                            for i=one,1,-1 do
                                local data = list[i]
                                if data then
                                    v = setmetatableindex({ free = free or false, p = p },last)
                                    list[one] = v
                                    break
                                end
                            end
                        end
                    end
                    t[k] = v
                    return v
                end
            end
            t[k] = false
            return false
        end)
        --
        setmetatableindex(tobesaved,function(t,k)
            if k then
                local prefix, one, two = lpegmatch(p_splitter,k)
                local v = rawget(t,prefix)
                if v and type(v) == "table" then
                    v = v[one]
                    if v and two then
                        v = v[two]
                    end
                    return v -- or default
                else
                 -- return default
                end
            end
        end)
        --
        setmetatablenewindex(tobesaved,function(t,k,v)
            if k then
                local prefix, one, two = lpegmatch(p_splitter,k)
                local p = rawget(t,prefix)
                if not p then
                    p = { }
                    rawset(t,prefix,p)
                end
                if type(one) == "number" then -- maybe Cc(0 1 2)
                    if #p < one then
                        for i=#p+1,one-1 do
                            p[i] = { } -- false
                        end
                    end
                end
                if two then
                    local pone = p[one]
                    if not pone then
                        pone = { }
                        p[one] = pone
                    end
                    if type(two) == "number" then -- maybe Cc(0 1 2)
                        if #pone < two then
                            for i=#pone+1,two-1 do
                                pone[i] = { } -- false
                            end
                        end
                    end
                    pone[two] = v
                else
                    p[one] = v
                end
            end
        end)
        --
        syncdata = setmetatableindex(function(t,category)
            -- p's and y's are not shared so no need to resolve
            local list = rawget(collected,"syncpos")
            local tc = list and rawget(list,category)
            if tc then
                sort(tc,function(a,b)
                    local ap = a.p
                    local bp = b.p
                    if ap == bp then
                        return b.y < a.y
                    else
                        return ap < bp
                    end
                end)
                tc.start = 1
                for i=1,#tc do
                    checkcommondata(tc[i],x_h_d_list)
                end
            else
                tc = { }
            end
            t[category] = tc
            return tc
        end)
    end

    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.

        local nofpositions = 0
        local nofpartials  = 0
        local nofdeltas    = 0
        --
        local x_y_w_h_size = 0
        local x_y_w_h_list = { }
        local x_y_w_h_hash = setmetatableindex(function(t,x)
            local y = setmetatableindex(function(t,y)
                local w = setmetatableindex(function(t,w)
                    local h = setmetatableindex(function(t,h)
                        x_y_w_h_size = x_y_w_h_size + 1
                        t[h] = x_y_w_h_size
                        x_y_w_h_list[x_y_w_h_size] = { x = x, y = y, w = w, h = h }
                        return x_y_w_h_size
                    end)
                    t[w] = h
                    return h
                end)
                t[y] = w
                return w
            end)
            t[x] = y
            return y
        end)
        --
        local y_w_h_d_size = 0
        local y_w_h_d_list = { }
        local y_w_h_d_hash = setmetatableindex(function(t,y)
            local w = setmetatableindex(function(t,w)
                local h = setmetatableindex(function(t,h)
                    local d = setmetatableindex(function(t,d)
                        y_w_h_d_size = y_w_h_d_size + 1
                        t[d] = y_w_h_d_size
                        y_w_h_d_list[y_w_h_d_size] = { y = y, w = w, h = h, d = d }
                        return y_w_h_d_size
                    end)
                    t[h] = d
                    return d
                end)
                t[w] = h
                return h
            end)
            t[y] = w
            return w
        end)
        --
        local x_h_d_size = 0
        local x_h_d_list = { }
        local x_h_d_hash = setmetatableindex(function(t,x)
            local h = setmetatableindex(function(t,h)
                local d = setmetatableindex(function(t,d)
                    x_h_d_size = x_h_d_size + 1
                    t[d] = x_h_d_size
                    x_h_d_list[x_h_d_size] = { x = x, h = h, d = d }
                    return x_h_d_size
                end)
                t[h] = d
                return d
            end)
            t[x] = h
            return h
        end)
        --
        local x_h_d_hs_size = 0
        local x_h_d_hs_list = { }
        local x_h_d_hs_hash = setmetatableindex(function(t,x)
            local h = setmetatableindex(function(t,h)
                local d = setmetatableindex(function(t,d)
                    local hs = setmetatableindex(function(t,hs)
                        x_h_d_hs_size = x_h_d_hs_size + 1
                        t[hs] = x_h_d_hs_size
                        x_h_d_hs_list[x_h_d_hs_size] = { x = x, h = h, d = d, hs = hs }
                        return x_h_d_hs_size
                    end)
                    t[d] = hs
                    return hs
                end)
                t[h] = d
                return d
            end)
            t[x] = h
            return h
        end)
        --
        rawset(tobesaved,"shared", {
            x_y_w_h  = x_y_w_h_list,
            y_w_h_d  = y_w_h_d_list,
            x_h_d    = x_h_d_list,
            x_h_d_hs = x_h_d_hs_list,
        })
        --
        -- If fonts can use crazy and hard to grasp packing tricks so can we. The "i" field
        -- refers to a shared set of values. In addition we pack some sequences.
        --
        -- how about free
        --
        for k, v in sortedhash(tobesaved) do
            if k == "p" then
                -- numeric
                local n = #v
                for i=1,n do
                    local t = v[i]
                    local hsh = x_h_d_hs_hash[t.x or 0][t.h or 0][t.d or 0][t.hs or 0]
                    t.x = nil
                    t.h = nil
                    t.d = nil
                    t.hs = nil -- not in syncpos
                    t.i = hsh
                    local s = t.s
                    if s then
                        checkshapes(s)
                    end
                end
                if deltapacking then
                    -- delta packing (y)
                    local last
                    local current
                    for i=1,n do
                        current = v[i]
                        if last then
                            for k, v in next, last do
                                if k ~= "y" and v ~= current[k] then
                                    goto DIFFERENT
                                end
                            end
                            for k, v in next, current do
                                if k ~= "y" and v ~= last[k] then
                                    goto DIFFERENT
                                end
                            end
                            v[i] = current.y or 0
                            nofdeltas = nofdeltas + 1
                            goto CONTINUE
                        end
                      ::DIFFERENT::
                        last = current
                      ::CONTINUE::
                    end
                end
                --
                nofpositions = nofpositions + n
                nofpartials  = nofpartials  + n
            elseif k == "syncpos" then
                -- hash
                for k, t in next, v do
                    -- numeric
                    local n = #t
                    for j=1,n do
                        local t = t[j]
                        local hsh = x_h_d_hash[t.x or 0][t.h or 0][t.d or 0]
                        t.x = nil
                        t.h = nil
                        t.d = nil
                        t.i = hsh
                    end
                    nofpositions = nofpositions + n
                    nofpartials  = nofpartials  + n
                end
            elseif k == "text" or k == "textarea" then
                -- numeric
                local n = #v
                for i=1,n do
                    local t = v[i]
                    local hsh = x_y_w_h_hash[t.x or 0][t.y or 0][t.w or 0][t.h or 0]
                    t.x = nil
                    t.y = nil
                    t.w = nil
                    t.h = nil
                    t.i = hsh
                end
                nofpositions = nofpositions + n
                nofpartials  = nofpartials  + n
                if deltapacking then
                    -- delta packing (p)
                    local last
                    local current
                    for i=1,n do
                        current = v[i]
                        if last then
                            for k, v in next, last do
                                if k ~= "p" and v ~= current[k] then
                                    goto DIFFERENT
                                end
                            end
                            for k, v in next, current do
                                if k ~= "p" and v ~= last[k] then
                                    goto DIFFERENT
                                end
                            end
                            v[i] = current.p or 0
                            nofdeltas = nofdeltas + 1
                            goto CONTINUE
                        end
                      ::DIFFERENT::
                        last = current
                      ::CONTINUE::
                    end
                end
            elseif k == "columnarea" then
                -- numeric
                local n = #v
                for i=1,n do
                    local t = v[i]
                    local hsh = y_w_h_d_hash[t.y or 0][t.w or 0][t.h or 0][t.d or 0]
                    t.y = nil
                    t.w = nil
                    t.h = nil
                    t.d = nil
                    t.i = hsh
                end
                nofpositions = nofpositions + n
                nofpartials  = nofpartials  + n
            else -- probably only b has shapes
                for k, t in next, v do -- no need to sort
                    local s = t.s
                    if s then
                        checkshapes(s)
                    end
                    nofpositions = nofpositions + 1
                end
            end
        end

        statistics.register("positions", function()
            if nofpositions > 0 then
                return format("%s collected, %i deltas, %i shared partials, %i partial entries",
                    nofpositions, nofdeltas, nofpartials,
                    x_y_w_h_size + y_w_h_d_size + x_h_d_size + x_h_d_hs_size
                )
            else
                return nil
            end
        end)

    end

    freedata = setmetatableindex(function(t,page)
        local list = rawget(collected,"free")
        local free = { }
        if list then
            local size = 0
            for i=1,#list do
                local l = list[i]
                if l.p == page then
                    size = size + 1
                    free[size] = l
                    checkcommondata(l)
                end
            end
            sort(free,function(a,b) return b.y < a.y end) -- order matters !
        end
        t[page] = free
        return free
    end)

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

else

    columndata = setmetatableindex("table") -- per page
    freedata   = setmetatableindex("table") -- per page

    local function initializer()
        tobesaved = jobpositions.tobesaved
        collected = jobpositions.collected
        --
        local pagedata   = { }
        local p_splitter = lpeg.splitat(":",true)

        for tag, data in next, collected do
            local prefix, rest = lpegmatch(p_splitter,tag)
            if prefix == "page" then
                pagedata[tonumber(rest) or 0] = data
            elseif prefix == "free" then
                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 -- never used

    end

    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.

        local nofpositions = 0

        for k, v in next, tobesaved do
            local s = v.s
            if s then
                checkshapes(s)
            end
            nofpositions = nofpositions + 1
        end

        statistics.register("positions", function()
            if nofpositions > 0 then
                return format("%s collected",nofpositions)
            else
                return nil
            end
        end)

    end

    local p_number = lpeg.patterns.cardinal/tonumber
    local p_tag    = P("syncpos:") * p_number * P(":") * p_number

    syncdata = setmetatableindex(function(t,category)
        setmetatable(t,nil)
        for tag, pos in next, collected do
            local c, n = lpegmatch(p_tag,tag)
            if c then
                local tc = t[c]
                if tc then
                    tc[n] = pos
                else
                    t[c] = { [n] = pos }
                end
            end
        end
        for k, list in next, t do
            sort(list,function(a,b)
                local ap = a.p
                local bp = b.p
                if ap == bp then
                    return b.y < a.y
                else
                    return ap < bp
                end
            end)
            list.start = 1
        end
        return t[category]
    end)

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

end

function jobpositions.used()
    if positionsused == nil then
        positionsused = false
        for k, v in next, collected do
            if k ~= "shared" and type(v) == "table" and next(v) then
                positionsused = true
                break
            end
        end
    end
    return positionsused
end

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

function jobpositions.getsync(category)
    return syncdata[category] or { }
end

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, gethvrpos, gethpos, getvpos, getrpos

function jobpositions.registerhandlers(t)
    getpos    = t and t.getpos    or function() return 0, 0 end
    gethvrpos = t and t.gethvrpos 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
    getrpos   = t and t.getrpos   or function() return 0 end
end

function jobpositions.getpos   () return getpos   () end
function jobpositions.gethvrpos() return gethvrpos() end
function jobpositions.gethpos  () return gethpos  () end
function jobpositions.getvpos  () return getvpos  () end
function jobpositions.getrpos  () return getrpos  () 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(c_inlinelefttoright) == 1 and true or nil,
        dir = texget("pardirection") == 1 and true or nil,
    }
end

local function enhance(data)
    if not data then
        return nil
    end
    local r = data.r
    if r == true then -- or ""
        data.r = region
        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.r2l then
--     print(">>>",data.r2l,getrpos())
-- end
-- local r2l = getrpos()
-- if r2l == 1 then
--     data.r2l = nil
-- end
    -- This is a quick hack. We also need to handel the other text areas
    -- end. It has to do with backgrounds (see mathincontext-setups.tex)

    if r then
        local dr = tobesaved[r]
        if dr then
            local sy = dr.sy
            if sy and sy ~= 1 then
                data.y = (data.y or 0) * sy
                data.h = (data.h or 0) * sy
                data.d = (data.d or 0) * sy
            end
        end
    end

    -- End of hack.

    if data.p == true then
        data.p = texgetcount(c_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
        local 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
        local 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",
    public    = true,
    protected = true,
 -- arguments = { "argument", "integerargument", "dimenargument", "dimenargument" },
    arguments = { "argument", "integer", "dimension", "dimension" },
    actions   = setall, -- name p x y
}

implement {
    name      = "dosavepositionwhd",
    public    = true,
    protected = true,
 -- arguments = { "argument", "integerargument", "dimenargument", "dimenargument", "dimenargument", "dimenargument", "dimenargument" },
    arguments = { "argument", "integer", "dimension", "dimension", "dimension", "dimension", "dimension" },
    actions   = setall, -- name p x y w h d
}

implement {
    name      = "dosavepositionplus",
    public    = true,
    protected = true,
 -- arguments = { "argument", "integerargument", "dimenargument", "dimenargument", "dimenargument", "dimenargument", "dimenargument", "argument" },
    arguments = { "argument", "integer", "dimension", "dimension", "dimension", "dimension", "dimension", "argument" },
    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 = "argument",
    actions   = function(tag)
        insert(columns,tag)
        column = tag
    end
}

implement {
    name      = "bposcolumnregistered",
    arguments = "argument",
    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]
    if last then
        local x, y = getpos()
        last.x = x ~= 0 and x or nil
        last.y = y ~= 0 and y or nil
        last.p = texgetcount(c_realpageno)
        insert(regions,tag) -- todo: fast stack
        region = tag
    end
end

local function e_region(specification)
    local last = tobesaved[region]
    if last then
        local y = getvpos()
        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
end

jobpositions.b_region = b_region
jobpositions.e_region = e_region

local lastregion

local function setregionbox(n,tag,index,depth,yscale,column,k,lo,ro,to,bo) -- no correct here
    if not tag or tag == "" then
        nofregions = nofregions + 1
        tag   = "region"
        index = nofregions
    elseif index ~= 0 then
        -- So we can cheat and pass a zero index and enforce tag as is needed in
        -- cases where we fallback on automated region tagging (framed).
        tag = tag .. ":" .. index
    end
    local box = getbox(n)
    local w, h, d = getwhd(box)
    -- We could set directly but then we also need to check for gaps but as this
    -- is direct is is unlikely that we get a gap. We then also need to intecept
    -- these auto regions (comning from framed). Too messy and the split in the
    -- setter is fast enough.
    if depth then
        d = d + depth
    end
    if yscale and (yscale == 0 or yscale == 1) then
        yscale = nil
    end
    tobesaved[tag] = {
     -- p  = texgetcount(c_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,
        sy = yscale
    }
    lastregion = tag
    return tag, box
end

-- we can have a finalizer property that we catch in the backend but that demands
-- a check for property for each list .. what is the impact

-- textarea operates *inside* a box so experiments with pre/post hooks in the
-- backend driver didn't work out (because a box can be larger)
--
-- it also gives no gain to split prefix and number here because in the end we
-- push and pop tags as strings, but it save a little on expansion so we do it
-- in the interface

local function markregionbox(n,tag,index,depth,yscale,column,correct,...) -- correct needs checking
    local tag, box = setregionbox(n,tag,index,depth,yscale,column,...)
     -- 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

do

    local c_anch_positions_paragraph <const> = texiscount("c_anch_positions_paragraph")

    local nofparagraphs = 0

    local function enhancepar_1(data)
        if data then
            local par   = data.par -- we can pass twice when we copy
            local state = par and getparstate(data.par,true)
            if state then
                local x, y = getpos()
                if x ~= 0 then
                    data.x  = x
                end
                if y ~= 0 then
                    data.y  = y
                end
                data.p = texgetcount(c_realpageno) -- we should use a variable set in otr
                if column then
                    data.c = column
                end
                if region then
                    data.r = region
                end
                --
                data.par         = nil
                local leftskip   = state.leftskip
                local rightskip  = state.rightskip
                local hangindent = state.hangindent
                local hangafter  = state.hangafter
                local parindent  = state.parindent
                local parshape   = state.parshape
                if hangafter ~= 0 and hangafter ~= 1 then
                    data.ha = hangafter
                end
                if hangindent ~= 0 then
                    data.hi = hangindent
                end
                data.hs = state.hsize
                if leftskip ~= 0 then
                    data.ls = leftskip
                end
                if parindent ~= 0 then
                    data.pi = parindent
                end
                if rightskip ~= 0 then
                    data.rs = rightskip
                end
                if parshape and #parshape > 0 then
                    data.ps = parshape
                end
            end
        end
        return data
    end

    local function enhancepar_2(data)
        if data then
            local x, y = getpos()
            if x ~= 0 then
                data.x  = x
            end
            if y ~= 0 then
                data.y  = y
            end
            data.p = texgetcount(c_realpageno)
            if column then
                data.c = column
            end
            if region then
                data.r = region
            end
        end
        return data
    end

    implement {
        name    = "parpos",
        actions = function()
            nofparagraphs = nofparagraphs + 1
            texsetcount("global",c_anch_positions_paragraph,nofparagraphs)
            local name = f_p_tag(nofparagraphs)
            local h = texgetdimen(d_strutht)
            local d = texgetdimen(d_strutdp)
            --
            local top = texgetnest("top","head")
            local nxt = top.next
            if nxt then
                nxt = tonut(nxt)
            end
            local data
            if nxt and getid(nxt) == par_code then -- todo: check node type
                local t = {
                    h   = h,
                    d   = d,
                    par = nxt,
                }
                tobesaved[name] = t
                ctx_latelua { action = enhancepar_1, specification = t }
            else
                -- This is kind of weird but it happens in tables (rows) so we probably
                -- need less.
                local state      = texgetparstate()
                local leftskip   = state.leftskip
                local rightskip  = state.rightskip
                local hangindent = state.hangindent
                local hangafter  = state.hangafter
                local parindent  = state.parindent
                local parshape   = state.parshape
                local t = {
                    p  = true,
                    c  = true,
                    r  = true,
                    x  = true,
                    y  = true,
                    h  = h,
                    d  = d,
                    hs = state.hsize, -- never 0
                }
                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
                tobesaved[name] = t
                ctx_latelua { action = enhancepar_2, specification = t }
            end
        end
    }

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

    implement {
        name      = "dosetpositionwhd",
     -- arguments = { "argument", "dimenargument", "dimenargument", "dimenargument" },
        arguments = { "argument", "dimension", "dimension", "dimension" },
        public    = true,
        protected = true,
        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(c_inlinelefttoright) == 1 or nil,
                dir = texget("pardirection") == 1 and true or nil,
           }
            tobesaved[name] = spec
            ctx_latelua { action = enhance, specification = spec }
        end
    }

    implement {
        name      = "dosetpositionbox",
     -- arguments = { "argument", "integerargument" },
        arguments = { "argument", "integer" },
        public    = true,
        protected = true,
        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(c_inlinelefttoright) == 1 or nil,
                dir = texget("pardirection") == 1 and true or nil,
            }
            tobesaved[name] = spec
            ctx_latelua { action = enhance, specification = spec }
        end
    }

    implement {
        name      = "dosetpositionplus",
     -- arguments = { "argument", "dimenargument", "dimenargument", "dimenargument" },
        arguments = { "argument", "dimension", "dimension", "dimension" },
        public    = true,
        protected = true,
        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(c_inlinelefttoright) == 1 or nil,
                dir = texget("pardirection") == 1 and true or nil,
            }
            tobesaved[name] = spec
            ctx_latelua { action = enhance, specification = spec }
        end
    }

    implement {
        name      = "dosetpositionstrut",
        arguments = "argument",
        public    = true,
        protected = true,
        actions   = function(name)
            local h = texgetdimen(d_strutht)
            local d = texgetdimen(d_strutdp)
            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(c_inlinelefttoright) == 1 or nil,
                dir = texget("pardirection") == 1 and true or nil,
            }
            tobesaved[name] = spec
            ctx_latelua { action = enhance, specification = spec }
        end
    }

    implement {
        name      = "dosetpositionstrutkind",
     -- arguments = { "argument", "integerargument" },
        arguments = { "argument", "integer" },
        public    = true,
        protected = true,
        actions   = function(name,kind)
            local h = texgetdimen(d_strutht)
            local d = texgetdimen(d_strutdp)
            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(c_inlinelefttoright) == 1 or nil,
                dir = texget("pardirection") == 1 and true or nil,
            }
            tobesaved[name] = spec
            ctx_latelua { action = enhance, specification = spec }
        end
    }

end

function jobpositions.getreserved(tag,n)
    if tag == v_column then
        local fulltag = f_tag_three(tag,texgetcount(c_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(c_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)
    local c = collected[id]
    if c then
        c.p = p ; c.x = x ; c.y = y ; c.w = w ; c.h = h ; c.d = d ; -- c g
    else
        collected[i] = { p = p, x = x, y = y, w = w, h = h, d = d } -- c g
    end
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.w, 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,"([^,%s]+)") 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

local function getcolumndata(realpage,column)
    local p = columndata[realpage]
    if p then
        return p[column]
    end
end

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

-- interface

implement {
    name      = "replacepospxywhd",
 -- arguments = { "argument", "integerargument", "dimenargument", "dimenargument", "dimenargument", "dimenargument", "dimenargument" },
    arguments = { "argument", "integer", "dimension", "dimension", "dimension", "dimension", "dimension" },
    public    = true,
    protected = true,
    actions   = function(name,page,x,y,w,h,d)
        local c = collected[name]
        if c then
            c.p = page ; c.x = x ; c.y = y ; c.w = w ; c.h = h ; c.d = d ;
        else
            collected[name] = { p = page, x = x, y = y, w = w, h = h, d = d }
        end
    end
}

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

implement {
    name      = "MPp",
    arguments = "argument",
    public    = true,
    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      = "jobposp",
        arguments = "argument",
        public    = true,
        usage     = "value",
        actions   = function(name)
            local jpi = collected[name]
            if jpi then
                local p = jpi.p
                if p and p ~= true then
                    return integer_code, p
                end
            end
            return integer_code, 0
        end
    }


implement {
    name      = "MPx",
    arguments = "argument",
    public    = true,
    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      = "jobposx",
        arguments = "argument",
        public    = true,
        usage     = "value",
        actions   = function(name)
            local jpi = collected[name]
            if jpi then
                local x = jpi.x
                if x and x ~= true and x ~= 0 then
                    return dimension_code, x*pt
                end
            end
            return dimension_code, 0
        end
    }

implement {
    name      = "MPy",
    arguments = "argument",
    public    = true,
    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      = "jobposy",
        arguments = "argument",
        public    = true,
        usage     = "value",
        actions   = function(name)
            local jpi = collected[name]
            if jpi then
                local y = jpi.y
                if y and y ~= true and y ~= 0 then
                    return dimension_code, y*pt
                end
            end
            return dimension_code, 0
        end
    }

implement {
    name      = "MPw",
    arguments = "argument",
    public    = true,
    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      = "jobposw",
        arguments = "argument",
        public    = true,
        usage     = "value",
        actions   = function(name)
            local jpi = collected[name]
            if jpi then
                local w = jpi.w
                if w and w ~= 0 then
                    return dimension_code, w*pt
                end
            end
            return dimension_code, 0
        end
    }

implement {
    name      = "MPh",
    arguments = "argument",
    public    = true,
    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      = "jobposh",
        arguments = "argument",
        public    = true,
        usage     = "value",
        actions   = function(name)
            local jpi = collected[name]
            if jpi then
                local w = jpi.h
                if h and h ~= 0 then
                    return dimension_code, h*pt
                end
            end
            return dimension_code, 0
        end
    }

implement {
    name      = "MPd",
    arguments = "argument",
    public    = true,
    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      = "jobposd",
        arguments = "argument",
        public    = true,
        usage     = "value",
        actions   = function(name)
            local jpi = collected[name]
            if jpi then
                local d = jpi.d
                if d and d ~= 0 then
                    return dimension_code, d*pt
                end
            end
            return dimension_code, 0
        end
    }

implement {
    name      = "MPxy",
    arguments = "argument",
    public    = true,
    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 = "argument",
    public    = true,
    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 = "argument",
    public    = true,
    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 = "argument",
    public    = true,
    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 = "argument",
    public    = true,
    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 = "argument",
    public    = true,
    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 = "argument",
    public    = true,
    actions   = MPpos
}

implement {
    name      = "MPn",
    arguments = "argument",
    public    = true,
    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 = "argument",
    public    = true,
    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 = "argument",
    public    = true,
    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 = "argument",
    public    = true,
    actions   = MPpardata
}

-- implement {
--     name      = "MPposset",
--     arguments = "argument",
--     public    = true,
--     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 = "argument",
    public    = true,
    actions   = function(name)
        local jpi = collected[name]
        if jpi then
            context("%.5Fpt",jpi.ls*pt)
        else
            context("0pt")
        end
    end
}

implement {
    name      = "MPrs",
    arguments = "argument",
    public    = true,
    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 = { "argument", "integerargument", "argument" },
    arguments = { "argument", "integer", "argument" },
    public    = true,
    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 = "2 arguments",
    public    = true,
    actions   = function(name,default)
        local jpi = collected[name]
        context(jpi and jpi.e or default)
    end
}

implement {
    name      = "MPxywhd",
    arguments = "argument",
    public    = true,
    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
}

implement {
    name      = "doifelseposition",
    arguments = "argument",
    public    = true,
    protected = true,
    actions   = function(name)
        ctx_doifelse(collected[name])
    end
}

implement {
    name      = "doifposition",
    arguments = "argument",
    public    = true,
    protected = true,
    actions   = function(name)
        ctx_doif(collected[name])
    end
}

implement {
    name      = "doifelsepositiononpage",
 -- arguments = { "string", "integerargument" },
    arguments = { "string", "integer" },
    public    = true,
    protected = true,
    actions   = function(name,p)
        local c = collected[name]
        ctx_doifelse(c and c.p == p)
    end
}

implement {
    name      = "doifelseoverlapping",
    arguments = "2 arguments",
    public    = true,
    protected = true,
    actions   = function(one,two)
        ctx_doifelse(overlapping(one,two))
    end
}

implement {
    name      = "doifelsepositionsonsamepage",
    arguments = "argument",
    public    = true,
    protected = true,
    actions   = function(list)
        ctx_doifelse(onsamepage(list))
    end
}

implement {
    name      = "doifelsepositionsonthispage",
    arguments = "argument",
    public    = true,
    protected = true,
    actions   = function(list)
        ctx_doifelse(onsamepage(list,tostring(texgetcount(c_realpageno))))
    end
}

implement {
    name      = "doifelsepositionsused",
    public    = true,
    protected = true,
    actions   = function()
        ctx_doifelse(jobpositions.used())
    end
}

implement {
    name      = "setregionbox",
    arguments = "2 integers",
    actions   = setregionbox
}

implement {
    name      = "setregionboxtagged",
    arguments = { "integer", "string", "integer", "dimension", "posit" },
    actions   = function(box,tag,index,depth,yscale)
        setregionbox(box,tag,index,depth,fromposit(yscale)) -- n text index depth yscale
    end
}

implement {
    name      = "markregionbox",
--     arguments = "2 integers",
    arguments = "integer",
    actions   = markregionbox
}

implement {
    name      = "markregionboxtagged",
    arguments = { "integer", "string", "integer", "dimension", "posit" },
    actions   = function(box,tag,index,depth,yscale)
        markregionbox(box,tag,index,depth,fromposit(yscale),false,false)
    end
}

implement {
    name      = "markregionboxtaggedn",
    arguments = { "integer", "string", "integer", "dimension", "posit", "integer" },
    actions   = function(box,tag,index,depth,yscale,column)
        markregionbox(box,tag,index,depth,fromposit(yscale),column,false)
    end
}

implement {
    name      = "markregionboxcorrected",
    arguments = { "integer", "string", "integer", "dimension", "posit" },
    actions   = function(box,tag,index,depth,yscale)
        markregionbox(box,tag,index,depth,fromposit(yscale),false,true)
    end
}

implement {
    name      = "markregionboxtaggedkind",
    arguments = { "integer", "string", "integer", "dimension", "posit", "integer", "dimension", "dimension", "dimension", "dimension" },
    actions   = function(box,tag,index,depth,yscale,kind,d1,d2,d3,d4)
        markregionbox(box,tag,index,depth,fromposit(yscale),false,false,kind,d1,d2,d3,d4)
    end
}

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

-- We support the low level positional commands too:

local newsavepos = nodes.pool.savepos

jobpositions.lastx = 0
jobpositions.lasty = 0
jobpositions.lastr = 0

implement {
    name       = "savepos",
    protected = true,
    public    = true,
    actions   = function()
        context(newsavepos()) -- or return a node
    end
}

implement {
    name      = "lastxpos",
    usage     = "value",
    protected = true,
    public    = true,
    actions   = function()
        return dimension_code, jobpositions.lastx
    end
}

implement {
    name      = "lastypos",
    usage     = "value",
    protected = true,
    public    = true,
    actions   = function()
        return dimension_code, jobpositions.lasty
    end
}

implement {
    name      = "lastrpos",
    usage     = "value",
    protected = true,
    public    = true,
    actions   = function()
        return integer_code, jobpositions.lastr
    end
}
