if not modules then modules = { } end modules ['lpdf-mis'] = {
    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"
}

-- Although we moved most pdf handling to the lua end, we didn't change
-- the overall approach. For instance we share all resources i.e. we
-- don't make subsets for each xform or page. The current approach is
-- quite efficient. A big difference between MkII and MkIV is that we
-- now use forward references. In this respect the MkII code shows that
-- it evolved over a long period, when backends didn't provide forward
-- referencing and references had to be tracked in multiple passes. Of
-- course there are a couple of more changes.

local next, tostring, type = next, tostring, type
local format, gsub, formatters = string.format, string.gsub, string.formatters
local concat, flattened = table.concat, table.flattened
local settings_to_array = utilities.parsers.settings_to_array

local pdfbackend           = backends and backends.registered.pdf or { }
local nodeinjections       = pdfbackend.nodeinjections
local codeinjections       = pdfbackend.codeinjections
local registrations        = pdfbackend.registrations

local getpagedimensions    = layouts.getpagedimensions
local getcanvas            = layouts.getcanvas

local nodes                = nodes
local nuts                 = nodes.nuts
local copy_node            = nuts.copy

local nodepool             = nuts.pool
local setstate             = nodepool.setstate
local register             = nodepool.register

local lpdf                 = lpdf
local pdfdictionary        = lpdf.dictionary
local pdfarray             = lpdf.array
local pdfconstant          = lpdf.constant
local pdfreference         = lpdf.reference
local pdfunicode           = lpdf.unicode
local pdfverbose           = lpdf.verbose
local pdfstring            = lpdf.string
local pdfaction            = lpdf.action
local pdfflushobject       = lpdf.flushobject
local pdfflushstreamobject = lpdf.flushstreamobject
local pdfmajorversion      = lpdf.majorversion
local pdfminorversion      = lpdf.minorversion

local adddocumentextgstate = lpdf.adddocumentextgstate
local addtocatalog         = lpdf.addtocatalog
local addtoinfo            = lpdf.addtoinfo
local addtopageattributes  = lpdf.addtopageattributes
local addtonames           = lpdf.addtonames

local texset               = tex.set

local variables            = interfaces.variables

local v_stop               <const> = variables.stop
local v_none               <const> = variables.none
local v_max                <const> = variables.max
local v_bookmark           <const> = variables.bookmark
local v_fit                <const> = variables.fit
local v_doublesided        <const> = variables.doublesided
local v_singlesided        <const> = variables.singlesided
local v_default            <const> = variables.default
local v_auto               <const> = variables.auto
local v_fixed              <const> = variables.fixed
local v_landscape          <const> = variables.landscape
local v_portrait           <const> = variables.portrait
local v_page               <const> = variables.page
local v_paper              <const> = variables.paper
local v_attachment         <const> = variables.attachment
local v_layer              <const> = variables.layer
local v_lefttoright        <const> = variables.lefttoright
local v_righttoleft        <const> = variables.righttoleft
local v_title              <const> = variables.title
local v_nomenubar          <const> = variables.nomenubar

local positive             = register(setstate("/GSpositive gs"))
local negative             = register(setstate("/GSnegative gs"))
local overprint            = register(setstate("/GSoverprint gs"))
local knockout             = register(setstate("/GSknockout gs"))

local omitextraboxes       = false

directives.register("backend.omitextraboxes", function(v) omitextraboxes = v end)

local function initializenegative()
    local a = pdfarray { 0, 1 }
    local g = pdfconstant("ExtGState")
    local d = pdfdictionary {
        FunctionType = 4,
        Range        = a,
        Domain       = a,
    }
    local negative = pdfdictionary { Type = g, TR = pdfreference(pdfflushstreamobject("{ 1 exch sub }",d)) } -- can be shared
    local positive = pdfdictionary { Type = g, TR = pdfconstant("Identity") }
    adddocumentextgstate("GSnegative", pdfreference(pdfflushobject(negative)))
    adddocumentextgstate("GSpositive", pdfreference(pdfflushobject(positive)))
    initializenegative = nil
end

local function initializeoverprint()
    local g = pdfconstant("ExtGState")
    local knockout  = pdfdictionary { Type = g, OP = false, OPM  = 0 }
    local overprint = pdfdictionary { Type = g, OP = true,  OPM  = 1 }
    adddocumentextgstate("GSknockout",  pdfreference(pdfflushobject(knockout)))
    adddocumentextgstate("GSoverprint", pdfreference(pdfflushobject(overprint)))
    initializeoverprint = nil
end

function nodeinjections.overprint()
    if initializeoverprint then initializeoverprint() end
    return copy_node(overprint)
end
function nodeinjections.knockout ()
    if initializeoverprint then initializeoverprint() end
    return copy_node(knockout)
end

function nodeinjections.positive()
    if initializenegative then initializenegative() end
    return copy_node(positive)
end
function nodeinjections.negative()
    if initializenegative then initializenegative() end
    return copy_node(negative)
end

-- function codeinjections.addtransparencygroup()
--     -- png: /CS /DeviceRGB /I true
--     local d = pdfdictionary {
--         S = pdfconstant("Transparency"),
--         I = true,
--         K = true,
--     }
--     lpdf.registerpagefinalizer(function() addtopageattributes("Group",d) end) -- hm
-- end

-- actions (todo: store and update when changed)

local openpage, closepage, opendocument, closedocument

function codeinjections.registerdocumentopenaction(open)
    opendocument = open
end

function codeinjections.registerdocumentcloseaction(close)
    closedocument = close
end

function codeinjections.registerpageopenaction(open)
    openpage = open
end

function codeinjections.registerpagecloseaction(close)
    closepage = close
end

local function flushdocumentactions()
    if opendocument then
        addtocatalog("OpenAction",pdfaction(opendocument))
    end
    if closedocument then
        addtocatalog("CloseAction",pdfaction(closedocument))
    end
end

local function flushpageactions()
    if openpage or closepage then
        local d = pdfdictionary()
        if openpage then
            d.O = pdfaction(openpage)
        end
        if closepage then
            d.C = pdfaction(closepage)
        end
        addtopageattributes("AA",d)
    end
end

lpdf.registerpagefinalizer    (flushpageactions,    "page actions")
lpdf.registerdocumentfinalizer(flushdocumentactions,"document actions")

-- the code above will move to scrn-ini

-- or when we want to be able to set things after page 1:
--
-- lpdf.registerdocumentfinalizer(setupidentity,1,"identity")

local function flushjavascripts()
    local t = interactions.javascripts.flushpreambles()
    if #t > 0 then
        local a = pdfarray()
        local pdf_javascript = pdfconstant("JavaScript")
        for i=1,#t do
            local ti     = t[i]
            local name   = ti[1]
            local script = ti[2]
            local j = pdfdictionary {
                S  = pdf_javascript,
                JS = pdfreference(pdfflushstreamobject(script)),
            }
            a[#a+1] = pdfstring(name)
            a[#a+1] = pdfreference(pdfflushobject(j))
        end
        addtonames("JavaScript",pdfreference(pdfflushobject(pdfdictionary{ Names = a })))
    end
end

lpdf.registerdocumentfinalizer(flushjavascripts,"javascripts")

-- -- --

local plusspecs = {
    [v_max] = {
        mode = "FullScreen",
    },
    [v_bookmark] = {
        mode = "UseOutlines",
    },
    [v_attachment] = {
        mode = "UseAttachments",
    },
    [v_layer] = {
        mode = "UseOC",
    },
    [v_fit] = {
        fit  = true,
    },
    [v_doublesided] = {
        layout = "TwoColumnRight",
    },
    [v_fixed] = {
        fixed  = true,
    },
    [v_landscape] = {
        duplex = "DuplexFlipShortEdge",
    },
    [v_portrait] = {
        duplex = "DuplexFlipLongEdge",
    },
    [v_page] = {
        duplex = "Simplex" ,
    },
    [v_paper] = {
        paper  = true,
    },
    [v_title] ={
        title = true,
    },
    [v_lefttoright] ={
        direction = "L2R",
    },
    [v_righttoleft] ={
        direction = "R2L",
    },
    [v_nomenubar] ={
        nomenubar = true,
    },
}

local pagespecs = {
    --
    [v_max]         = plusspecs[v_max],
    [v_bookmark]    = plusspecs[v_bookmark],
    [v_attachment]  = plusspecs[v_attachment],
    [v_layer]       = plusspecs[v_layer],
    [v_lefttoright] = plusspecs[v_lefttoright],
    [v_righttoleft] = plusspecs[v_righttoleft],
    [v_title]       = plusspecs[v_title],
    --
    [v_none] = {
    },
    [v_fit] = {
        mode = "UseNone",
        fit  = true,
    },
    [v_doublesided] = {
        mode   = "UseNone",
        layout = "TwoColumnRight",
        fit    = true,
    },
    [v_singlesided] = {
        mode   = "UseNone"
    },
    [v_default] = {
        mode   = "UseNone",
        layout = "auto",
    },
    [v_auto] = {
        mode   = "UseNone",
        layout = "auto",
    },
    [v_fixed] = {
        mode   = "UseNone",
        layout = "auto",
        fixed  = true, -- noscale
    },
    [v_landscape] = {
        mode   = "UseNone",
        layout = "auto",
        fixed  = true,
        duplex = "DuplexFlipShortEdge",
    },
    [v_portrait] = {
        mode   = "UseNone",
        layout = "auto",
        fixed  = true,
        duplex = "DuplexFlipLongEdge",
    },
    [v_page] = {
        mode   = "UseNone",
        layout = "auto",
        fixed  = true,
        duplex = "Simplex",
    },
    [v_paper] = {
        mode   = "UseNone",
        layout = "auto",
        fixed  = true,
        duplex = "Simplex",
        paper  = true,
    },
    [v_nomenubar] = {
        mode      = "UseNone",
        layout    = "auto",
        nomenubar = true,
    },
}

local function documentspecification()
    local canvas   = getcanvas()

    -- move this to layo-ini ?

    local pagespec = canvas.pagespec
    if not pagespec or pagespec == "" then
        pagespec = v_default
    end
    local settings = settings_to_array(pagespec)
    -- so the first one detemines the defaults
    local first    = settings[1]
    local defaults = pagespecs[first]
    local spec     = defaults or pagespecs[v_default]
    -- successive keys can modify this
    if spec.layout == "auto" then
        if canvas.doublesided then
            local s = pagespecs[v_doublesided] -- to be checked voor interfaces
            for k, v in next, s do
                spec[k] = v
            end
        else
            spec.layout = false
        end
    end
    -- we start at 2 when we have a valid first default set
    for i=defaults and 2 or 1,#settings do
        local s = plusspecs[settings[i]]
        if s then
            for k, v in next, s do
                spec[k] = v
            end
        end
    end
    -- maybe interfaces.variables
    local layout    = spec.layout
    local mode      = spec.mode
    local fit       = spec.fit
    local fixed     = spec.fixed
    local duplex    = spec.duplex
    local paper     = spec.paper
    local title     = spec.title
    local direction = spec.direction
    local nomenubar = spec.nomenubar
    if layout then
        addtocatalog("PageLayout",pdfconstant(layout))
    end
    if mode then
        addtocatalog("PageMode",pdfconstant(mode))
    end
    local prints = nil
    local marked = canvas.marked
    local copies = canvas.copies
    if marked then
        local pages     = structures.pages
        local marked    = pages.allmarked(marked)
        local nofmarked = marked and #marked or 0
        if nofmarked > 0 then
            -- the spec is wrong in saying that numbering starts at 1 which of course makes
            -- sense as most real documents start with page 0 .. sigh
            for i=1,#marked do marked[i] = marked[i] - 1 end
            prints = pdfarray(flattened(pages.toranges(marked)))
        end
    end
    -- The ua spec demands that the DisplayDocTitle should be set, with the argument
    -- that otherwise viewers can't show it correctly. Who cares anyway.
 -- if fit or fixed or duplex or copies or paper or prints or title or direction or nomenubar then
        addtocatalog("ViewerPreferences",pdfdictionary {
            FitWindow         = fit       and true                   or nil,
            PrintScaling      = fixed     and pdfconstant("None")    or nil,
            Duplex            = duplex    and pdfconstant(duplex)    or nil,
            NumCopies         = copies    and copies                 or nil,
            PickTrayByPDFSize = paper     and true                   or nil,
            PrintPageRange    = prints                               or nil,
            DisplayDocTitle   = true,
            Direction         = direction and pdfconstant(direction) or nil,
            HideMenubar       = nomenubar and true                   or nil,
        })
 -- end
 -- addtoinfo   ("Trapped", pdfconstant("False")) -- '/Trapped' in /Info, 'Trapped' in XMP
    addtocatalog("Version", pdfconstant(format("%s.%s",pdfmajorversion(),pdfminorversion())))
 -- local language = languages.names[texgetcount("mainlanguagenumber")]
    addtocatalog("Lang",    pdfstring(tokens.getters.macro("currentmainlanguage")))
end

local bpfactor <const> = number.dimenfactors.bp

local function pagespecification()
    local canvas      = getcanvas()
    local paperwidth  = canvas.paperwidth
    local paperheight = canvas.paperheight
    local leftoffset  = canvas.leftoffset
    local topoffset   = canvas.topoffset
    --
    local llx = leftoffset
    local urx = canvas.width - leftoffset
    local lly = paperheight + topoffset - canvas.height
    local ury = paperheight - topoffset
    -- boxes can be cached
    local function extrabox(WhatBox,offset,always)
        if offset ~= 0 or always then
            addtopageattributes(WhatBox, pdfarray {
                (llx + offset) * bpfactor,
                (lly + offset) * bpfactor,
                (urx - offset) * bpfactor,
                (ury - offset) * bpfactor,
            })
        end
    end
    if omitextraboxes then
        -- only useful for testing / comparing
    else
        extrabox("CropBox",canvas.cropoffset,true) -- mandate for rendering
        extrabox("TrimBox",canvas.trimoffset,true) -- mandate for pdf/x
        extrabox("BleedBox",canvas.bleedoffset)    -- optional
     -- extrabox("ArtBox",canvas.artoffset)        -- optional .. unclear what this is meant to do
    end
end

lpdf.registerpagefinalizer(pagespecification,"page specification")
lpdf.registerdocumentfinalizer(documentspecification,"document specification")

-- Page Label support ...
--
-- In principle we can also support /P (prefix) as we can just use the verbose form
-- and we can then forget about the /St (start) as we don't care about those few
-- extra bytes due to lack of collapsing. Anyhow, for that we need a stupid prefix
-- variant and that's not on the agenda now.

-- % \disabledirectives[backends.pdf.pagelabels]
-- \setupfittingpage[pagestate=start] % make page labels okay

local enabled = true

directives.register("backend.pdf.pagelabels", function(v)
    enabled = v
end)

local map = {
    numbers       = "D",
    Romannumerals = "R",
    romannumerals = "r",
    Characters    = "A",
    characters    = "a",
}

local function featurecreep()
    if not enabled then
        return
    end
    --
    local pages         = structures.pages.tobesaved
    local list          = pdfarray()
    local getset        = structures.sets.get
    local stopped       = false
    local oldprefix     = nil
    local oldconversion = nil
    for i=1,#pages do
        local p = pages[i]
        if not p then
            return -- fatal error
        end
        local prefix = p.viewerprefix or ""
        if p.state == v_stop then
            if not stopped then
                list[#list+1] = i - 1 -- pdf starts numbering at 0
                list[#list+1] = pdfdictionary {
                    P = pdfunicode(prefix),
                }
                stopped = true
            end
            oldprefix     = nil
            oldconversion = nil
            stopped       = false
        else
            local numberdata = p.numberdata
            local conversion = nil
            local number     = p.number
            if numberdata then
                local conversionset = numberdata.conversionset
                if conversionset then
                    conversion = getset("structure:conversions",p.block,conversionset,1,"numbers")
                end
            end
            -- If needed we can do some preroll on a prefix (prefix) but this is a rather useless
            -- feature (creep) anyway so why bother.
            conversion = conversion and map[conversion] or map.numbers
            if number == 1 or oldprefix ~= prefix or oldconversion ~= conversion then
                list[#list+1] = i - 1 -- pdf starts numbering at 0
                list[#list+1] = pdfdictionary {
                    S  = pdfconstant(conversion),
                    St = number > 0 and number or 1, -- there is no 'nonumber' and we need to be >= 1
                    P  = prefix ~= "" and pdfunicode(prefix) or nil,
                }
            end
            oldprefix     = prefix
            oldconversion = conversion
            stopped       = false
        end
    end
    addtocatalog("PageLabels", pdfdictionary { Nums = list })
end

lpdf.registerdocumentfinalizer(featurecreep,"featurecreep")
