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

local setmetatable, getmetatable, type, next, tostring, tonumber, rawset = setmetatable, getmetatable, type, next, tostring, tonumber, rawset
local char, byte, format, gsub, concat, match, sub, gmatch = string.char, string.byte, string.format, string.gsub, table.concat, string.match, string.sub, string.gmatch
local utfchar, utfvalues = utf.char, utf.values
local sind, cosd, floor, max, min = math.sind, math.cosd, math.floor, math.max, math.min
local lpegmatch, P, C, R, S, Cc, Cs = lpeg.match, lpeg.P, lpeg.C, lpeg.R, lpeg.S, lpeg.Cc, lpeg.Cs
local formatters = string.formatters

local report_objects    = logs.reporter("backend","objects")
local report_finalizing = logs.reporter("backend","finalizing")
local report_blocked    = logs.reporter("backend","blocked")

-- gethpos              : used
-- getpos               : used
-- getvpos              : used
--
-- getmatrix            : used
-- hasmatrix            : used
--
-- mapfile              : used in font-ctx.lua
-- mapline              : used in font-ctx.lua
--
-- maxobjnum            : not used
-- obj                  : used
-- immediateobj         : used
-- objtype              : not used
-- pageref              : used
-- print                : can be used
-- refobj               : used
-- registerannot        : not to be used
-- reserveobj           : used

-- pdf.catalog          : used
-- pdf.info             : used
-- pdf.trailer          : used
-- pdf.names            : not to be used

-- pdf.setinfo          : used
-- pdf.setcatalog       : used
-- pdf.setnames         : not to be used
-- pdf.settrailer       : used

-- pdf.getinfo          : used
-- pdf.getcatalog       : used
-- pdf.getnames         : not to be used
-- pdf.gettrailer       : used

local pdf    = pdf
local factor = number.dimenfactors.bp

if pdf.setinfo then
--     table.setmetatablenewindex(pdf,function(t,k,v)
--         report_blocked("'pdf.%s' is not supported",k)
--     end)
    -- the getters are harmless
end

if not pdf.setinfo then
    function pdf.setinfo   (s) pdf.info    = s end
    function pdf.setcatalog(s) pdf.catalog = s end
    function pdf.setnames  (s) pdf.names   = s end
    function pdf.settrailer(s) pdf.trailer = s end
end

if not pdf.getpos then
    function pdf.getpos   () return pdf.h, pdf.v     end
    function pdf.gethpos  () return pdf.h            end
    function pdf.getvpos  () return pdf.v            end
    function pdf.hasmatrix() return false            end
    function pdf.getmatrix() return 1, 0, 0, 1, 0, 0 end
end

if not pdf.setpageresources then
    function pdf.setpageresources  (s) pdf.pageresources   = s end
    function pdf.setpageattributes (s) pdf.pageattributes  = s end
    function pdf.setpagesattributes(s) pdf.pagesattributes = s end
end

local pdfsetinfo            = pdf.setinfo
local pdfsetcatalog         = pdf.setcatalog
local pdfsetnames           = pdf.setnames
local pdfsettrailer         = pdf.settrailer

local pdfsetpageresources   = pdf.setpageresources
local pdfsetpageattributes  = pdf.setpageattributes
local pdfsetpagesattributes = pdf.setpagesattributes

local pdfgetpos             = pdf.getpos
local pdfgethpos            = pdf.gethpos
local pdfgetvpos            = pdf.getvpos
local pdfgetmatrix          = pdf.getmatrix
local pdfhasmatrix          = pdf.hasmatrix

local pdfreserveobject      = pdf.reserveobj
local pdfimmediateobject    = pdf.immediateobj
local pdfdeferredobject     = pdf.obj
local pdfreferenceobject    = pdf.refobj

function pdf.setinfo           () report_blocked("'pdf.%s' is not supported","setinfo")            end -- use lpdf.addtoinfo etc
function pdf.setcatalog        () report_blocked("'pdf.%s' is not supported","setcatalog")         end
function pdf.setnames          () report_blocked("'pdf.%s' is not supported","setnames")           end
function pdf.settrailer        () report_blocked("'pdf.%s' is not supported","settrailer")         end
function pdf.setpageresources  () report_blocked("'pdf.%s' is not supported","setpageresources")   end
function pdf.setpageattributes () report_blocked("'pdf.%s' is not supported","setpageattributes")  end
function pdf.setpagesattributes() report_blocked("'pdf.%s' is not supported","setpagesattributes") end

function pdf.registerannot() report_blocked("'pdf.%s' is not supported","registerannot") end

local trace_finalizers = false  trackers.register("backend.finalizers", function(v) trace_finalizers = v end)
local trace_resources  = false  trackers.register("backend.resources",  function(v) trace_resources  = v end)
local trace_objects    = false  trackers.register("backend.objects",    function(v) trace_objects    = v end)
local trace_detail     = false  trackers.register("backend.detail",     function(v) trace_detail     = v end)

local backends   = backends
local pdfbackend = {
    comment        = "backend for directly generating pdf output",
    nodeinjections = { },
    codeinjections = { },
    registrations  = { },
    tables         = { },
}
backends.pdf     = pdfbackend
lpdf             = lpdf or { }
local lpdf       = lpdf

local codeinjections = pdfbackend.codeinjections
local nodeinjections = pdfbackend.nodeinjections

codeinjections.getpos    = pdfgetpos     lpdf.getpos    = pdfgetpos
codeinjections.gethpos   = pdfgethpos    lpdf.gethpos   = pdfgethpos
codeinjections.getvpos   = pdfgetvpos    lpdf.getvpos   = pdfgetvpos
codeinjections.hasmatrix = pdfhasmatrix  lpdf.hasmatrix = pdfhasmatrix
codeinjections.getmatrix = pdfgetmatrix  lpdf.getmatrix = pdfgetmatrix

function lpdf.transform(llx,lly,urx,ury)
    if pdfhasmatrix() then
        local sx, rx, ry, sy = pdfgetmatrix()
        local w, h = urx - llx, ury - lly
        return llx, lly, llx + sy*w - ry*h, lly + sx*h - rx*w
    else
        return llx, lly, urx, ury
    end
end

-- function lpdf.rectangle(width,height,depth)
--     local h, v = pdfgetpos()
--     local llx, lly, urx, ury
--     if pdfhasmatrix() then
--         local sx, rx, ry, sy = pdfgetmatrix()
--         llx = 0
--         lly = -depth
--      -- llx =                ry * depth
--      -- lly = -sx * depth
--         urx =  sy * width  - ry * height
--         ury =  sx * height - rx * width
--     else
--         llx = 0
--         lly = -depth
--         urx = width
--         ury = height
--         return (h+llx)*factor, (v+lly)*factor, (h+urx)*factor, (v+ury)*factor
--     end
-- end

function lpdf.rectangle(width,height,depth)
    local h, v = pdfgetpos()
    if pdfhasmatrix() then
        local sx, rx, ry, sy = pdfgetmatrix()
     -- return (h+ry*depth)*factor, (v-sx*depth)*factor, (h+sy*width-ry*height)*factor, (v+sx*height-rx*width)*factor
        return h           *factor, (v-   depth)*factor, (h+sy*width-ry*height)*factor, (v+sx*height-rx*width)*factor
    else
        return h           *factor, (v-   depth)*factor, (h+   width          )*factor, (v+   height         )*factor
    end
end

--

local function tosixteen(str) -- an lpeg might be faster (no table)
    if not str or str == "" then
        return "<feff>" -- not () as we want an indication that it's unicode
    else
        local r, n = { "<feff" }, 1
        for b in utfvalues(str) do
            n = n + 1
            if b < 0x10000 then
                r[n] = format("%04x",b)
            else
             -- r[n] = format("%04x%04x",b/1024+0xD800,b%1024+0xDC00)
                r[n] = format("%04x%04x",floor(b/1024),b%1024+0xDC00)
            end
        end
        n = n + 1
        r[n] = ">"
        return concat(r)
    end
end

lpdf.tosixteen   = tosixteen

-- lpeg is some 5 times faster than gsub (in test) on escaping

-- local escapes = {
--     ["\\"] = "\\\\",
--     ["/"] = "\\/", ["#"] = "\\#",
--     ["<"] = "\\<", [">"] = "\\>",
--     ["["] = "\\[", ["]"] = "\\]",
--     ["("] = "\\(", [")"] = "\\)",
-- }
--
-- local escaped = Cs(Cc("(") * (S("\\/#<>[]()")/escapes + P(1))^0 * Cc(")"))
--
-- local function toeight(str)
--     if not str or str == "" then
--         return "()"
--     else
--         return lpegmatch(escaped,str)
--     end
-- end
--
-- -- no need for escaping .. just use unicode instead

-- \0 \t \n \r \f <space> ( ) [ ] { } / %

local function toeight(str)
    return "(" .. str .. ")"
end

lpdf.toeight = toeight

-- local escaped = lpeg.Cs((lpeg.S("\0\t\n\r\f ()[]{}/%")/function(s) return format("#%02X",byte(s)) end + lpeg.P(1))^0)
--
-- local function cleaned(str)
--     return (str and str ~= "" and lpegmatch(escaped,str)) or ""
-- end
--
-- lpdf.cleaned = cleaned -- not public yet

local function merge_t(a,b)
    local t = { }
    for k,v in next, a do t[k] = v end
    for k,v in next, b do t[k] = v end
    return setmetatable(t,getmetatable(a))
end

local f_key_value      = formatters["/%s %s"]
local f_key_dictionary = formatters["/%s << % t >>"]
local f_dictionary     = formatters["<< % t >>"]
local f_key_array      = formatters["/%s [ % t ]"]
local f_array          = formatters["[ % t ]"]
local f_key_number     = formatters["/%s %F"]
local f_tonumber       = formatters["%F"]

-- local f_key_value      = formatters["/%s %s"]
-- local f_key_dictionary = formatters["/%s <<% t>>"]
-- local f_dictionary     = formatters["<<% t>>"]
-- local f_key_array      = formatters["/%s [% t]"]
-- local f_array          = formatters["[% t]"]

local tostring_a, tostring_d

tostring_d = function(t,contentonly,key)
    if next(t) then
        local r, rn = { }, 0
        for k, v in next, t do
            rn = rn + 1
            local tv = type(v)
            if tv == "string" then
                r[rn] = f_key_value(k,toeight(v))
            elseif tv == "number" then
                r[rn] = f_key_number(k,v)
            elseif tv == "unicode" then
                r[rn] = f_key_value(k,tosixteen(v))
            elseif tv == "table" then
                local mv = getmetatable(v)
                if mv and mv.__lpdftype then
                    r[rn] = f_key_value(k,tostring(v))
                elseif v[1] then
                    r[rn] = f_key_value(k,tostring_a(v))
                else
                    r[rn] = f_key_value(k,tostring_d(v))
                end
            else
                r[rn] = f_key_value(k,tostring(v))
            end
        end
        if contentonly then
            return concat(r," ")
        elseif key then
            return f_key_dictionary(key,r)
        else
            return f_dictionary(r)
        end
    elseif contentonly then
        return ""
    else
        return "<< >>"
    end
end

tostring_a = function(t,contentonly,key)
    local tn = #t
    if tn ~= 0 then
        local r = { }
        for k=1,tn do
            local v = t[k]
            local tv = type(v)
            if tv == "string" then
                r[k] = toeight(v)
            elseif tv == "number" then
                r[k] = f_tonumber(v)
            elseif tv == "unicode" then
                r[k] = tosixteen(v)
            elseif tv == "table" then
                local mv = getmetatable(v)
                local mt = mv and mv.__lpdftype
                if mt then
                    r[k] = tostring(v)
                elseif v[1] then
                    r[k] = tostring_a(v)
                else
                    r[k] = tostring_d(v)
                end
            else
                r[k] = tostring(v)
            end
        end
        if contentonly then
            return concat(r, " ")
        elseif key then
            return f_key_array(key,r)
        else
            return f_array(r)
        end
    elseif contentonly then
        return ""
    else
        return "[ ]"
    end
end

local tostring_x = function(t) return concat(t," ")    end
local tostring_s = function(t) return toeight(t[1])    end
local tostring_u = function(t) return tosixteen(t[1])  end
local tostring_n = function(t) return tostring(t[1])   end -- tostring not needed
local tostring_n = function(t) return f_tonumber(t[1]) end -- tostring not needed
local tostring_c = function(t) return t[1]             end -- already prefixed (hashed)
local tostring_z = function()  return "null"           end
local tostring_t = function()  return "true"           end
local tostring_f = function()  return "false"          end
local tostring_r = function(t) local n = t[1] return n and n > 0 and (n .. " 0 R") or "NULL" end

local tostring_v = function(t)
    local s = t[1]
    if type(s) == "table" then
        return concat(s)
    else
        return s
    end
end

local function value_x(t)     return t                  end -- the call is experimental
local function value_s(t,key) return t[1]               end -- the call is experimental
local function value_u(t,key) return t[1]               end -- the call is experimental
local function value_n(t,key) return t[1]               end -- the call is experimental
local function value_c(t)     return sub(t[1],2)        end -- the call is experimental
local function value_d(t)     return tostring_d(t,true) end -- the call is experimental
local function value_a(t)     return tostring_a(t,true) end -- the call is experimental
local function value_z()      return nil                end -- the call is experimental
local function value_t(t)     return t.value or true    end -- the call is experimental
local function value_f(t)     return t.value or false   end -- the call is experimental
local function value_r()      return t[1] or 0          end -- the call is experimental -- NULL
local function value_v()      return t[1]               end -- the call is experimental

local function add_x(t,k,v) rawset(t,k,tostring(v)) end

local mt_x = { __lpdftype = "stream",     __tostring = tostring_x, __call = value_x, __newindex = add_x }
local mt_d = { __lpdftype = "dictionary", __tostring = tostring_d, __call = value_d }
local mt_a = { __lpdftype = "array",      __tostring = tostring_a, __call = value_a }
local mt_u = { __lpdftype = "unicode",    __tostring = tostring_u, __call = value_u }
local mt_s = { __lpdftype = "string",     __tostring = tostring_s, __call = value_s }
local mt_n = { __lpdftype = "number",     __tostring = tostring_n, __call = value_n }
local mt_c = { __lpdftype = "constant",   __tostring = tostring_c, __call = value_c }
local mt_z = { __lpdftype = "null",       __tostring = tostring_z, __call = value_z }
local mt_t = { __lpdftype = "true",       __tostring = tostring_t, __call = value_t }
local mt_f = { __lpdftype = "false",      __tostring = tostring_f, __call = value_f }
local mt_r = { __lpdftype = "reference",  __tostring = tostring_r, __call = value_r }
local mt_v = { __lpdftype = "verbose",    __tostring = tostring_v, __call = value_v }

local function pdfstream(t) -- we need to add attributes
    if t then
        for i=1,#t do
            t[i] = tostring(t[i])
        end
    end
    return setmetatable(t or { },mt_x)
end

local function pdfdictionary(t)
    return setmetatable(t or { },mt_d)
end

local function pdfarray(t)
    if type(t) == "string" then
        return setmetatable({ t },mt_a)
    else
        return setmetatable(t or { },mt_a)
    end
end

local function pdfstring(str,default)
    return setmetatable({ str or default or "" },mt_s)
end

local function pdfunicode(str,default)
    return setmetatable({ str or default or "" },mt_u)
end

local cache = { } -- can be weak

local function pdfnumber(n,default) -- 0-10
    n = n or default
    local c = cache[n]
    if not c then
        c = setmetatable({ n },mt_n)
    --  cache[n] = c -- too many numbers
    end
    return c
end

for i=-1,9 do cache[i] = pdfnumber(i) end

local cache = { } -- can be weak

local forbidden, replacements = "\0\t\n\r\f ()[]{}/%%#\\", { } -- table faster than function

for s in gmatch(forbidden,".") do
    replacements[s] = format("#%02x",byte(s))
end

local escaped = Cs(Cc("/") * (S(forbidden)/replacements + P(1))^0)

local function pdfconstant(str,default)
    str = str or default or ""
    local c = cache[str]
    if not c then
     -- c = setmetatable({ "/" .. str },mt_c)
        c = setmetatable({ lpegmatch(escaped,str) },mt_c)
        cache[str] = c
    end
    return c
end

local p_null  = { } setmetatable(p_null, mt_z)
local p_true  = { } setmetatable(p_true, mt_t)
local p_false = { } setmetatable(p_false,mt_f)

local function pdfnull()
    return p_null
end

--~ print(pdfboolean(false),pdfboolean(false,false),pdfboolean(false,true))
--~ print(pdfboolean(true),pdfboolean(true,false),pdfboolean(true,true))
--~ print(pdfboolean(nil,true),pdfboolean(nil,false))

local function pdfboolean(b,default)
    if type(b) == "boolean" then
        return b and p_true or p_false
    else
        return default and p_true or p_false
    end
end

local r_zero = setmetatable({ 0 },mt_r)

local function pdfreference(r)  -- maybe make a weak table
    if r and r ~= 0 then
        return setmetatable({ r },mt_r)
    else
        return r_zero
    end
end

local v_zero  = setmetatable({ 0  },mt_v)
local v_empty = setmetatable({ "" },mt_v)

local function pdfverbose(t) -- maybe check for type
    if t == 0 then
        return v_zero
    elseif t == "" then
        return v_empty
    else
        return setmetatable({ t },mt_v)
    end
end

lpdf.stream      = pdfstream -- THIS WILL PROBABLY CHANGE
lpdf.dictionary  = pdfdictionary
lpdf.array       = pdfarray
lpdf.string      = pdfstring
lpdf.unicode     = pdfunicode
lpdf.number      = pdfnumber
lpdf.constant    = pdfconstant
lpdf.null        = pdfnull
lpdf.boolean     = pdfboolean
lpdf.reference   = pdfreference
lpdf.verbose     = pdfverbose

local names, cache = { }, { }

function lpdf.reserveobject(name)
    local r = pdfreserveobject() -- we don't support "annot"
    if name then
        names[name] = r
        if trace_objects then
            report_objects("reserving number %a under name %a",r,name)
        end
    elseif trace_objects then
        report_objects("reserving number %a",r)
    end
    return r
end

-- lpdf.immediateobject = pdfimmediateobject
-- lpdf.deferredobject  = pdfdeferredobject
-- lpdf.object          = pdfdeferredobject
-- lpdf.referenceobject = pdfreferenceobject

local pagereference = pdf.pageref or tex.pdfpageref
local nofpages      = 0

function lpdf.pagereference(n)
    if nofpages == 0 then
        nofpages = structures.pages.nofpages
        if nofpages == 0 then
            nofpages = 1
        end
    end
    if n > nofpages then
        return pagereference(nofpages) -- or 1, could be configureable
    else
        return pagereference(n)
    end
end

function lpdf.delayedobject(data,n)
    if n then
        pdfdeferredobject(n,data)
    else
        n = pdfdeferredobject(data)
    end
    pdfreferenceobject(n)
    return n
end

function lpdf.flushobject(name,data)
    if data then
        local named = names[name]
        if named then
            if not trace_objects then
            elseif trace_detail then
                report_objects("flushing data to reserved object with name %a, data: %S",name,data)
            else
                report_objects("flushing data to reserved object with name %a",name)
            end
            return pdfimmediateobject(named,tostring(data))
        else
            if not trace_objects then
            elseif trace_detail then
                report_objects("flushing data to reserved object with number %s, data: %S",name,data)
            else
                report_objects("flushing data to reserved object with number %s",name)
            end
            return pdfimmediateobject(name,tostring(data))
        end
    else
        if trace_objects and trace_detail then
            report_objects("flushing data: %S",name)
        end
        return pdfimmediateobject(tostring(name))
    end
end


function lpdf.flushstreamobject(data,dict,compressed) -- default compressed
    if trace_objects then
        report_objects("flushing stream object of %s bytes",#data)
    end
    local dtype = type(dict)
    return pdfdeferredobject {
        immediate     = true,
        compresslevel = compressed == false and 0 or nil,
        type          = "stream",
        string        = data,
        attr          = (dtype == "string" and dict) or (dtype == "table" and dict()) or nil,
    }
end

function lpdf.flushstreamfileobject(filename,dict,compressed) -- default compressed
    if trace_objects then
        report_objects("flushing stream file object %a",filename)
    end
    local dtype = type(dict)
    return pdfdeferredobject {
        immediate     = true,
        compresslevel = compressed == false and 0 or nil,
        type          = "stream",
        file          = filename,
        attr          = (dtype == "string" and dict) or (dtype == "table" and dict()) or nil,
    }
end

local shareobjectcache, shareobjectreferencecache = { }, { }

function lpdf.shareobject(content)
    if content == nil then
        -- invalid object not created
    else
        content = tostring(content)
        local o = shareobjectcache[content]
        if not o then
            o = pdfimmediateobject(content)
            shareobjectcache[content] = o
        end
        return o
    end
end

function lpdf.shareobjectreference(content)
    if content == nil then
        -- invalid object not created
    else
        content = tostring(content)
        local r = shareobjectreferencecache[content]
        if not r then
            local o = shareobjectcache[content]
            if not o then
                o = pdfimmediateobject(content)
                shareobjectcache[content] = o
            end
            r = pdfreference(o)
            shareobjectreferencecache[content] = r
        end
        return r
    end
end

-- three priority levels, default=2

local pagefinalizers     = { { }, { }, { } }
local documentfinalizers = { { }, { }, { } }

local pageresources, pageattributes, pagesattributes

local function resetpageproperties()
    pageresources   = pdfdictionary()
    pageattributes  = pdfdictionary()
    pagesattributes = pdfdictionary()
end

resetpageproperties()

local function setpageproperties()
    pdfsetpageresources  (pageresources  ())
    pdfsetpageattributes (pageattributes ())
    pdfsetpagesattributes(pagesattributes())
end

local function addtopageresources  (k,v) pageresources  [k] = v end
local function addtopageattributes (k,v) pageattributes [k] = v end
local function addtopagesattributes(k,v) pagesattributes[k] = v end

lpdf.addtopageresources   = addtopageresources
lpdf.addtopageattributes  = addtopageattributes
lpdf.addtopagesattributes = addtopagesattributes

local function set(where,what,f,when,comment)
    if type(when) == "string" then
        when, comment = 2, when
    elseif not when then
        when = 2
    end
    local w = where[when]
    w[#w+1] = { f, comment }
    if trace_finalizers then
        report_finalizing("%s set: [%s,%s]",what,when,#w)
    end
end

local function run(where,what)
    if trace_finalizers then
        report_finalizing("start backend, category %a, n %a",what,#where)
    end
    for i=1,#where do
        local w = where[i]
        for j=1,#w do
            local wj = w[j]
            if trace_finalizers then
                report_finalizing("%s finalizer: [%s,%s] %s",what,i,j,wj[2] or "")
            end
            wj[1]()
        end
    end
    if trace_finalizers then
        report_finalizing("stop finalizing")
    end
end

local function registerpagefinalizer(f,when,comment)
    set(pagefinalizers,"page",f,when,comment)
end

local function registerdocumentfinalizer(f,when,comment)
    set(documentfinalizers,"document",f,when,comment)
end

lpdf.registerpagefinalizer     = registerpagefinalizer
lpdf.registerdocumentfinalizer = registerdocumentfinalizer

function lpdf.finalizepage(shipout)
    if shipout and not environment.initex then
     -- resetpageproperties() -- maybe better before
        run(pagefinalizers,"page")
        setpageproperties()
        resetpageproperties() -- maybe better before
    end
end

function lpdf.finalizedocument()
    if not environment.initex then
        run(documentfinalizers,"document")
        function lpdf.finalizedocument()
            report_finalizing("serious error: the document is finalized multiple times")
            function lpdf.finalizedocument() end
        end
    end
end

-- codeinjections.finalizepage = lpdf.finalizepage -- no longer triggered at the tex end

if not callbacks.register("finish_pdfpage", lpdf.finalizepage) then

    local find_tail    = nodes.tail
    local latelua_node = nodes.pool.latelua

    function nodeinjections.finalizepage(head)
        local t = find_tail(head.list)
        if t then
            local n = latelua_node("lpdf.finalizepage(true)") -- last in the shipout
            t.next = n
            n.prev = t
        end
        return head, true
    end

    nodes.tasks.appendaction("shipouts","normalizers","backends.pdf.nodeinjections.finalizepage")

end

callbacks.register("finish_pdffile", lpdf.finalizedocument)

-- some minimal tracing, handy for checking the order

local function trace_set(what,key)
    if trace_resources then
        report_finalizing("setting key %a in %a",key,what)
    end
end
local function trace_flush(what)
    if trace_resources then
        report_finalizing("flushing %a",what)
    end
end

lpdf.protectresources = true

local catalog = pdfdictionary { Type = pdfconstant("Catalog") } -- nicer, but when we assign we nil the Type
local info    = pdfdictionary { Type = pdfconstant("Info")    } -- nicer, but when we assign we nil the Type
----- names   = pdfdictionary { Type = pdfconstant("Names")   } -- nicer, but when we assign we nil the Type

local function flushcatalog() if not environment.initex then trace_flush("catalog") catalog.Type = nil pdfsetcatalog(catalog()) end end
local function flushinfo   () if not environment.initex then trace_flush("info")    info   .Type = nil pdfsetinfo   (info   ()) end end
-------------- flushnames  () if not environment.initex then trace_flush("names")   names  .Type = nil pdfsetnames  (names  ()) end end

function lpdf.addtocatalog(k,v) if not (lpdf.protectresources and catalog[k]) then trace_set("catalog",k) catalog[k] = v end end
function lpdf.addtoinfo   (k,v) if not (lpdf.protectresources and info   [k]) then trace_set("info",   k) info   [k] = v end end
-------- lpdf.addtonames  (k,v) if not (lpdf.protectresources and names  [k]) then trace_set("names",  k) names  [k] = v end end

local names = pdfdictionary {
 -- Type = pdfconstant("Names")
}

local function flushnames()
    if next(names) and not environment.initex then
        names.Type = pdfconstant("Names")
        trace_flush("names")
        lpdf.addtocatalog("Names",pdfreference(pdfimmediateobject(tostring(names))))
    end
end

function lpdf.addtonames(k,v)
    if not (lpdf.protectresources and names  [k]) then
        trace_set("names",  k)
        names  [k] = v
    end
end

local dummy = pdfreserveobject() -- else bug in hvmd due so some internal luatex conflict

-- Some day I will implement a proper minimalized resource management.

local r_extgstates,  d_extgstates  = pdfreserveobject(), pdfdictionary()  local p_extgstates  = pdfreference(r_extgstates)
local r_colorspaces, d_colorspaces = pdfreserveobject(), pdfdictionary()  local p_colorspaces = pdfreference(r_colorspaces)
local r_patterns,    d_patterns    = pdfreserveobject(), pdfdictionary()  local p_patterns    = pdfreference(r_patterns)
local r_shades,      d_shades      = pdfreserveobject(), pdfdictionary()  local p_shades      = pdfreference(r_shades)

local function checkextgstates () if next(d_extgstates ) then addtopageresources("ExtGState", p_extgstates ) end end
local function checkcolorspaces() if next(d_colorspaces) then addtopageresources("ColorSpace",p_colorspaces) end end
local function checkpatterns   () if next(d_patterns   ) then addtopageresources("Pattern",   p_patterns   ) end end
local function checkshades     () if next(d_shades     ) then addtopageresources("Shading",   p_shades     ) end end

local function flushextgstates () if next(d_extgstates ) then trace_flush("extgstates")  pdfimmediateobject(r_extgstates, tostring(d_extgstates )) end end
local function flushcolorspaces() if next(d_colorspaces) then trace_flush("colorspaces") pdfimmediateobject(r_colorspaces,tostring(d_colorspaces)) end end
local function flushpatterns   () if next(d_patterns   ) then trace_flush("patterns")    pdfimmediateobject(r_patterns,   tostring(d_patterns   )) end end
local function flushshades     () if next(d_shades     ) then trace_flush("shades")      pdfimmediateobject(r_shades,     tostring(d_shades     )) end end

function lpdf.collectedresources()
    local ExtGState  = next(d_extgstates ) and p_extgstates
    local ColorSpace = next(d_colorspaces) and p_colorspaces
    local Pattern    = next(d_patterns   ) and p_patterns
    local Shading    = next(d_shades     ) and p_shades
    if ExtGState or ColorSpace or Pattern or Shading then
        local collected = pdfdictionary {
            ExtGState  = ExtGState,
            ColorSpace = ColorSpace,
            Pattern    = Pattern,
            Shading    = Shading,
         -- ProcSet    = pdfarray { pdfconstant("PDF") },
        }
        return collected()
    else
        return ""
    end
end

function lpdf.adddocumentextgstate (k,v) d_extgstates [k] = v end
function lpdf.adddocumentcolorspace(k,v) d_colorspaces[k] = v end
function lpdf.adddocumentpattern   (k,v) d_patterns   [k] = v end
function lpdf.adddocumentshade     (k,v) d_shades     [k] = v end

registerdocumentfinalizer(flushextgstates,3,"extended graphic states")
registerdocumentfinalizer(flushcolorspaces,3,"color spaces")
registerdocumentfinalizer(flushpatterns,3,"patterns")
registerdocumentfinalizer(flushshades,3,"shades")

registerdocumentfinalizer(flushnames,3,"names") -- before catalog
registerdocumentfinalizer(flushcatalog,3,"catalog")
registerdocumentfinalizer(flushinfo,3,"info")

registerpagefinalizer(checkextgstates,3,"extended graphic states")
registerpagefinalizer(checkcolorspaces,3,"color spaces")
registerpagefinalizer(checkpatterns,3,"patterns")
registerpagefinalizer(checkshades,3,"shades")

-- in strc-bkm: lpdf.registerdocumentfinalizer(function() structures.bookmarks.place() end,1)

function lpdf.rotationcm(a)
    local s, c = sind(a), cosd(a)
    return format("%0.6F %0.6F %0.6F %0.6F 0 0 cm",c,s,-s,c)
end

-- ! -> universaltime

local timestamp = os.date("%Y-%m-%dT%X") .. os.timezone(true)

function lpdf.timestamp()
    return timestamp
end

function lpdf.pdftimestamp(str)
    local Y, M, D, h, m, s, Zs, Zh, Zm = match(str,"^(%d%d%d%d)%-(%d%d)%-(%d%d)T(%d%d):(%d%d):(%d%d)([%+%-])(%d%d):(%d%d)$")
    return Y and format("D:%s%s%s%s%s%s%s%s'%s'",Y,M,D,h,m,s,Zs,Zh,Zm)
end

function lpdf.id()
    return format("%s.%s",tex.jobname,timestamp)
end

function lpdf.checkedkey(t,key,variant)
    local pn = t and t[key]
    if pn then
        local tn = type(pn)
        if tn == variant then
            if variant == "string" then
                return pn ~= "" and pn or nil
            elseif variant == "table" then
                return next(pn) and pn or nil
            else
                return pn
            end
        elseif tn == "string" and variant == "number" then
            return tonumber(pn)
        end
    end
end

function lpdf.checkedvalue(value,variant) -- code not shared
    if value then
        local tv = type(value)
        if tv == variant then
            if variant == "string" then
                return value ~= "" and value
            elseif variant == "table" then
                return next(value) and value
            else
                return value
            end
        elseif tv == "string" and variant == "number" then
            return tonumber(value)
        end
    end
end

function lpdf.limited(n,min,max,default)
    if not n then
        return default
    else
        n = tonumber(n)
        if not n then
            return default
        elseif n > max then
            return max
        elseif n < min then
            return min
        else
            return n
        end
    end
end

-- lpdf.addtoinfo("ConTeXt.Version", tex.contextversiontoks)
-- lpdf.addtoinfo("ConTeXt.Time",    os.date("%Y.%m.%d %H:%M")) -- :%S
-- lpdf.addtoinfo("ConTeXt.Jobname", environment.jobname)
-- lpdf.addtoinfo("ConTeXt.Url",     "www.pragma-ade.com")

-- if not pdfreferenceobject then
--
--     local delayed = { }
--
--     local function flush()
--         local n = 0
--         for k,v in next, delayed do
--             pdfimmediateobject(k,v)
--             n = n + 1
--         end
--         if trace_objects then
--             report_objects("%s objects flushed",n)
--         end
--         delayed = { }
--     end
--
--     lpdf.registerdocumentfinalizer(flush,3,"objects") -- so we need a final flush too
--     lpdf.registerpagefinalizer    (flush,3,"objects") -- somehow this lags behind .. I need to look into that some day
--
--     function lpdf.delayedobject(data)
--         local n = pdfreserveobject()
--         delayed[n] = data
--         return n
--     end
--
-- end

-- setmetatable(pdf, {
--     __index = function(t,k)
--         if     k == "info"           then return pdf.getinfo()
--         elseif k == "catalog"        then return pdf.getcatalog()
--         elseif k == "names"          then return pdf.getnames()
--         elseif k == "trailer"        then return pdf.gettrailer()
--         elseif k == "pageattribute"  then return pdf.getpageattribute()
--         elseif k == "pageattributes" then return pdf.getpageattributes()
--         elseif k == "pageresources"  then return pdf.getpageresources()
--         elseif
--             return nil
--         end
--     end,
--     __newindex = function(t,k,v)
--         if     k == "info"           then return pdf.setinfo(v)
--         elseif k == "catalog"        then return pdf.setcatalog(v)
--         elseif k == "names"          then return pdf.setnames(v)
--         elseif k == "trailer"        then return pdf.settrailer(v)
--         elseif k == "pageattribute"  then return pdf.setpageattribute(v)
--         elseif k == "pageattributes" then return pdf.setpageattributes(v)
--         elseif k == "pageresources"  then return pdf.setpageresources(v)
--         else
--             rawset(t,k,v)
--         end
--     end,
-- })
