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 = math.sind, math.cosd
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 pdfreserveobject   = pdf.reserveobj
local pdfimmediateobject = pdf.immediateobj
local pdfdeferredobject  = pdf.obj
local pdfreferenceobject = pdf.refobj

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 report_objects    = logs.reporter("backend","objects")
local report_finalizing = logs.reporter("backend","finalizing")

local backends = backends

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

lpdf       = lpdf or { }
local lpdf = lpdf

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)
            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 tostring_a, tostring_d

tostring_d = function(t,contentonly,key)
    if not next(t) then
        if contentonly then
            return ""
        else
            return "<< >>"
        end
    else
        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 == "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
    end
end

tostring_a = function(t,contentonly,key)
    local tn = #t
    if tn == 0 then
        if contentonly then
            return ""
        else
            return "[ ]"
        end
    else
        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 == "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
    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_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 function pdfreference(r)
    return setmetatable({ r or 0 },mt_r)
end

local function pdfverbose(t) -- maybe check for type
    return setmetatable({ t or "" },mt_v)
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

-- n = pdf.obj(n, str)
-- n = pdf.obj(n, "file", filename)
-- n = pdf.obj(n, "stream", streamtext, attrtext)
-- n = pdf.obj(n, "streamfile", filename, attrtext)

-- we only use immediate objects

-- todo: tracing

local names, cache = { }, { }

function lpdf.reserveobject(name)
    if name == "annot" then
        -- catch misuse
        return pdfreserveobject("annot")
    else
        local r = pdfreserveobject()
        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
end

function lpdf.reserveannotation()
    return pdfreserveobject("annot")
end

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

lpdf.pagereference      = pdf.pageref or tex.pdfpageref
lpdf.registerannotation = pdf.registerannot

function lpdf.delayedobject(data) -- we will get rid of this one
    local n = pdfdeferredobject(data)
    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

--~ local d = lpdf.dictionary()
--~ local e = lpdf.dictionary { ["e"] = "abc", x = lpdf.dictionary { ["f"] = "ABC" }  }
--~ local f = lpdf.dictionary { ["f"] = "ABC" }
--~ local a = lpdf.array { lpdf.array { lpdf.string("xxx") } }

--~ print(a)
--~ os.exit()

--~ d["test"] = lpdf.string ("test")
--~ d["more"] = "more"
--~ d["bool"] = true
--~ d["numb"] = 1234
--~ d["oeps"] = lpdf.dictionary { ["hans"] = "ton" }
--~ d["whow"] = lpdf.array { lpdf.string("ton") }

--~ a[#a+1] = lpdf.string("xxx")
--~ a[#a+1] = lpdf.string("yyy")

--~ d.what = a

--~ print(e)

--~ local d = lpdf.dictionary()
--~ d["abcd"] = { 1, 2, 3, "test" }
--~ print(d)
--~ print(d())

--~ local d = lpdf.array()
--~ d[#d+1] = 1
--~ d[#d+1] = 2
--~ d[#d+1] = 3
--~ d[#d+1] = "test"
--~ print(d)

--~ local d = lpdf.array()
--~ d[#d+1] = { 1, 2, 3, "test" }
--~ print(d)

--~ local d = lpdf.array()
--~ d[#d+1] = { a=1, b=2, c=3, d="test" }
--~ print(d)

--~ local s = lpdf.constant("xx")
--~ print(s) -- fails somehow
--~ print(s()) -- fails somehow

--~ local s = lpdf.boolean(false)
--~ s.value = true
--~ print(s)
--~ print(s())

-- three priority levels, default=2

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

local pageresources, pageattributes, pagesattributes

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

resetpageproperties()

local function setpageproperties()
    pdf.pageresources   = pageresources  ()
    pdf.pageattributes  = pageattributes ()
    pdf.pagesattributes = 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()
    if 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

backends.pdf.codeinjections.finalizepage = lpdf.finalizepage      -- will go when we have hook

--~ callbacks.register("finish_pdfpage", lpdf.finalizepage)
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
local 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 pdf.catalog = catalog() end end
local function flushinfo   () if not environment.initex then trace_flush("info")    info   .Type = nil pdf.info    = info   () end end
local function flushnames  () if not environment.initex then trace_flush("names")   names  .Type = nil pdf.names   = 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
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(flushcatalog,3,"catalog")
registerdocumentfinalizer(flushinfo,3,"info")
registerdocumentfinalizer(flushnames,3,"names") -- before catalog

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
