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

-- It's about time to give up on media in pdf and admit that pdf lost it to html.
-- First we had movies and sound, quite easy to deal with, but obsolete now. Then we
-- had renditions but they turned out to be unreliable from the start and look
-- obsolete too or at least they are bound to the (obsolete) flash technology for
-- rendering. They were already complex constructs. Now we have rich media which
-- instead of providing a robust future proof framework for general media types
-- again seems to depend on viewers built in (yes, also kind of obsolete) flash
-- technology, and we cannot expect this non-open technology to show up in open
-- browsers. So, in the end we can best just use links to external resources to be
-- future proof. Just look at the viewer preferences pane to see how fragile support
-- is. Interestingly u3d support is kind of built in, while e.g. mp4 support relies
-- on wrapping in swf. We used to stay ahead of the pack with support of the fancy
-- pdf features but it backfires and is not worth the trouble. And yes, for control
-- (even simple like starting and stopping videos) one has to revert to JavaScript,
-- the other fragile bit. And, now that adobe quits flash in 2020 we're without any
-- video anyway. Also, it won't play on all platforms and devices so let's wait for
-- html5 media in pdf then.
--
-- See mail by Michal Vlasák to the mailing list that discusses current support in
-- viewers and also mentions (and submitted) a few fixes wrt embedding media.

local tonumber, next = tonumber, next
local gmatch, gsub, find, lower = string.gmatch, string.gsub, string.find, string.lower
local filenameonly, basefilename, filesuffix, addfilesuffix = file.nameonly, file.basename, file.suffix, file.addsuffix
local isfile, modificationtime = lfs.isfile, lfs.modification
local stripstring = string.strip
local settings_to_array = utilities.parsers.settings_to_array
local settings_to_hash = utilities.parsers.settings_to_hash
local sortedhash, sortedkeys = table.sortedhash, table.sortedkeys

local report_media             = logs.reporter("backend","media")
local report_attachment        = logs.reporter("backend","attachment")

local backends                 = backends
local lpdf                     = lpdf
local nodes                    = nodes
local context                  = context

local texgetcount              = tex.getcount

local hpacknode                = nodes.hpack

local nodeinjections           = backends.pdf.nodeinjections
local codeinjections           = backends.pdf.codeinjections
local registrations            = backends.pdf.registrations

local executers                = structures.references.executers
local variables                = interfaces.variables

local v_hidden                 = variables.hidden
local v_auto                   = variables.auto
local v_embed                  = variables.embed
local v_max                    = variables.max
local v_yes                    = variables.yes
local v_compress               = variables.compress

local pdfconstant              = lpdf.constant
local pdfnull                  = lpdf.null
local pdfdictionary            = lpdf.dictionary
local pdfarray                 = lpdf.array
local pdfreference             = lpdf.reference
local pdfunicode               = lpdf.unicode
local pdfstring                = lpdf.string
local pdfboolean               = lpdf.boolean
local pdfaction                = lpdf.action
local pdfborder                = lpdf.border

local pdftransparencyvalue     = lpdf.transparencyvalue
local pdfcolorvalues           = lpdf.colorvalues

local pdfflushobject           = lpdf.flushobject
local pdfflushstreamobject     = lpdf.flushstreamobject
local pdfflushstreamfileobject = lpdf.flushstreamfileobject
local pdfreserveobject         = lpdf.reserveobject
local pdfpagereference         = lpdf.pagereference
local pdfshareobjectreference  = lpdf.shareobjectreference

-- symbols

local presets = { } -- xforms

local function registersymbol(name,n)
    presets[name] = pdfreference(n)
end

local function registeredsymbol(name)
    return presets[name]
end

local function presetsymbol(symbol)
    if not presets[symbol] then
        context.predefinesymbol { symbol }
    end
end

local function presetsymbollist(list)
    if list then
        for symbol in gmatch(list,"[^, ]+") do
            presetsymbol(symbol)
        end
    end
end

codeinjections.registersymbol   = registersymbol
codeinjections.registeredsymbol = registeredsymbol
codeinjections.presetsymbol     = presetsymbol
codeinjections.presetsymbollist = presetsymbollist

-- comments

-- local symbols = {
--     Addition     = pdfconstant("NewParagraph"),
--     Attachment   = pdfconstant("Attachment"),
--     Balloon      = pdfconstant("Comment"),
--     Check        = pdfconstant("Check Mark"),
--     CheckMark    = pdfconstant("Check Mark"),
--     Circle       = pdfconstant("Circle"),
--     Cross        = pdfconstant("Cross"),
--     CrossHairs   = pdfconstant("Cross Hairs"),
--     Graph        = pdfconstant("Graph"),
--     InsertText   = pdfconstant("Insert Text"),
--     New          = pdfconstant("Insert"),
--     Paperclip    = pdfconstant("Paperclip"),
--     RightArrow   = pdfconstant("Right Arrow"),
--     RightPointer = pdfconstant("Right Pointer"),
--     Star         = pdfconstant("Star"),
--     Tag          = pdfconstant("Tag"),
--     Text         = pdfconstant("Note"),
--     TextNote     = pdfconstant("Text Note"),
--     UpArrow      = pdfconstant("Up Arrow"),
--     UpLeftArrow  = pdfconstant("Up-Left Arrow"),
-- }

local attachment_symbols = {
    Graph     = pdfconstant("Graph"),
    Paperclip = pdfconstant("Paperclip"),
    Pushpin   = pdfconstant("PushPin"),
}

attachment_symbols.PushPin = attachment_symbols.Pushpin
attachment_symbols.Default = attachment_symbols.Pushpin

function lpdf.attachmentsymbols()
    return sortedkeys(comment_symbols)
end

local comment_symbols = {
    Comment      = pdfconstant("Comment"),
    Help         = pdfconstant("Help"),
    Insert       = pdfconstant("Insert"),
    Key          = pdfconstant("Key"),
    Newparagraph = pdfconstant("NewParagraph"),
    Note         = pdfconstant("Note"),
    Paragraph    = pdfconstant("Paragraph"),
}

comment_symbols.NewParagraph = Newparagraph
comment_symbols.Default      = Note

function lpdf.commentsymbols()
    return sortedkeys(comment_symbols)
end

local function analyzesymbol(symbol,collection)
    if not symbol or symbol == "" then
        return collection and collection.Default, nil
    elseif collection and collection[symbol] then
        return collection[symbol], nil
    else
        local setn, setr, setd
        local set = settings_to_array(symbol)
        if #set == 1 then
            setn, setr, setd = set[1], set[1], set[1]
        elseif #set == 2 then
            setn, setr, setd = set[1], set[1], set[2]
        else
            setn, setr, setd = set[1], set[2], set[3]
        end
        local appearance = pdfdictionary {
            N = setn and registeredsymbol(setn),
            R = setr and registeredsymbol(setr),
            D = setd and registeredsymbol(setd),
        }
        local appearanceref = pdfshareobjectreference(appearance)
        return nil, appearanceref
    end
end

local function analyzenormalsymbol(symbol)
    local appearance = pdfdictionary {
        N = registeredsymbol(symbol),
    }
    local appearanceref = pdfshareobjectreference(appearance)
    return appearanceref
end

codeinjections.analyzesymbol       = analyzesymbol
codeinjections.analyzenormalsymbol = analyzenormalsymbol

local function analyzelayer(layer)
    -- todo:  (specification.layer ~= "" and pdfreference(specification.layer)) or nil, -- todo: ref to layer
end

local function analyzecolor(colorvalue,colormodel)
    local cvalue = colorvalue and tonumber(colorvalue)
    local cmodel = colormodel and tonumber(colormodel) or 3
    return cvalue and pdfarray { pdfcolorvalues(cmodel,cvalue) } or nil
end

local function analyzetransparency(transparencyvalue)
    local tvalue = transparencyvalue and tonumber(transparencyvalue)
    return tvalue and pdftransparencyvalue(tvalue) or nil
end

-- Attachments

local nofattachments    = 0
local attachments       = { }
local filestreams       = { }
local referenced        = { }
local ignorereferenced  = true -- fuzzy pdf spec .. twice in attachment list, can become an option
local tobesavedobjrefs  = utilities.storage.allocate()
local collectedobjrefs  = utilities.storage.allocate()
local permitted         = true
local enabled           = true

function codeinjections.setattachmentsupport(option)
    if option == false then
        permitted = false
        enabled   = false
    end
end

local fileobjreferences = {
    collected = collectedobjrefs,
    tobesaved = tobesavedobjrefs,
}

job.fileobjreferences = fileobjreferences

local function initializer()
    collectedobjrefs = job.fileobjreferences.collected or { }
    tobesavedobjrefs = job.fileobjreferences.tobesaved or { }
end

job.register('job.fileobjreferences.collected', tobesavedobjrefs, initializer)

local function flushembeddedfiles()
    if enabled and next(filestreams) then
        local e = pdfarray()
        local f = pdfarray()
        for tag, reference in sortedhash(filestreams) do
            if not reference then
                report_attachment("unreferenced file, tag %a",tag)
            elseif referenced[tag] == "hidden" or referenced[tag] == "forced" then
                e[#e+1] = pdfstring(tag)
                e[#e+1] = reference -- already a reference
                f[#f+1] = reference -- collect all file description references
            else
                -- messy spec ... when annot not in named else twice in menu list acrobat
                f[#f+1] = reference
            end
        end
        if #e > 0 then
            lpdf.addtonames("EmbeddedFiles",pdfreference(pdfflushobject(pdfdictionary{ Names = e })))
        end
        if #f > 0 then -- PDF/A-2|3: all associated files must have a relationship to the PDF document (global or part)
            lpdf.addtocatalog("AF", pdfreference(pdfflushobject(f))) -- global (Catalog)
        end
    end
end

lpdf.registerdocumentfinalizer(flushembeddedfiles,"embeddedfiles")

function codeinjections.embedfile(specification)
    if enabled then
        local data      = specification.data
        local filename  = specification.file
        local name      = specification.name or ""
        local title     = specification.title or ""
        local hash      = specification.hash or filename
        local keepdir   = specification.keepdir -- can change
        local usedname  = specification.usedname
        local filetype  = specification.filetype
        local compress  = specification.compress
        local mimetype  = specification.mimetype or specification.mime
        if filename == "" then
            filename = nil
        end
        if compress == nil then
            compress = true
        end
        if data then
            local r = filestreams[hash]
            if r == false then
                return nil
            elseif r then
                return r
            elseif not filename then
                filename = specification.tag
                if not filename or filename == "" then
                    filename = specification.registered
                end
                if not filename or filename == "" then
                    filename = hash
                end
            end
        else
            if not filename then
                return nil
            end
            local r = filestreams[hash]
            if r == false then
                return nil
            elseif r then
                return r
            else
                local foundname = resolvers.findbinfile(filename) or ""
                if foundname == "" or not isfile(foundname) then
                    filestreams[filename] = false
                    return nil
                else
                    specification.foundname = foundname
                end
            end
        end
        -- needs to be cleaned up:
        usedname = usedname ~= "" and usedname or filename or name
        local basename  = keepdir == true and usedname or basefilename(usedname)
        local basename  = gsub(basename,"%./","")
        local savename  = name ~= "" and name or basename
        local foundname = specification.foundname or filename
        if not filetype or filetype == "" then
            filetype = name and (filename and filesuffix(filename)) or "txt"
        end
        savename = addfilesuffix(savename,filetype) -- type is mandate for proper working in viewer
        local a = pdfdictionary {
            Type    = pdfconstant("EmbeddedFile"),
            Subtype = mimetype and mimetype ~= "" and pdfconstant(mimetype) or nil,
        }
        local f
        if data then
            f = pdfflushstreamobject(data,a)
            specification.data = true -- signal that still data but already flushed
        else
            local attributes   = lfs.attributes(foundname)
            local modification = modificationtime(foundname)
            a.Params = {
                Size    = attributes.size,
                ModDate = lpdf.pdftimestamp(modification),
            }
            f = pdfflushstreamfileobject(foundname,a,compress)
        end
        local d = pdfdictionary {
            Type           = pdfconstant("Filespec"),
            F              = pdfstring(savename),
         -- UF             = pdfstring(savename),
            UF             = pdfunicode(savename),
            EF             = pdfdictionary { F = pdfreference(f) },
            Desc           = title ~= "" and pdfunicode(title) or nil,
            AFRelationship = pdfconstant("Unspecified"), -- Supplement, Data, Source, Alternative, Data
        }
        local r = pdfreference(pdfflushobject(d))
        filestreams[hash] = r
        if specification.forcereference == true then
            referenced[hash] = "forced"
        end
        return r
    end
end

function nodeinjections.attachfile(specification)
    if enabled then
        local registered = specification.registered or "<unset>"
        local data = specification.data
        local hash
        local filename
        if data then
            hash = md5.HEX(data)
        else
            filename = specification.file
            if not filename or filename == "" then
                report_attachment("no file specified, using registered %a instead",registered)
                filename = registered
                specification.file = registered
            end
            local foundname = resolvers.findbinfile(filename) or ""
            if foundname == "" or not isfile(foundname) then
                report_attachment("invalid filename %a, ignoring registered %a",filename,registered)
                return nil
            else
                specification.foundname = foundname
            end
            hash = filename
        end
        specification.hash = hash
        nofattachments = nofattachments + 1
        local registered = specification.registered or ""
        local title      = specification.title      or ""
        local subtitle   = specification.subtitle   or ""
        local author     = specification.author     or ""
        local onlyname   = filename and filenameonly(filename) or ""
        if registered == "" then
            registered = filename
        end
        if author == "" and title ~= "" then
            author = title
            title  = onlyname or ""
        end
        if author == "" then
            author = onlyname or "<unknown>"
        end
        if title == "" then
            title = registered
        end
        if title == "" and filename then
            title = onlyname
        end
        local aref = attachments[registered]
        if not aref then
            aref = codeinjections.embedfile(specification)
            attachments[registered] = aref
        end
        local reference = specification.reference
        if reference and aref then
            tobesavedobjrefs[reference] = aref[1]
        end
        if not aref then
            report_attachment("skipping attachment, registered %a",registered)
            -- already reported
        elseif specification.method == v_hidden then
            referenced[hash] = "hidden"
        else
            referenced[hash] = "annotation"
            local name, appearance = analyzesymbol(specification.symbol,attachment_symbols)
            local flags = specification.flags or 0 -- to keep it expandable
            local d = pdfdictionary {
                Subtype  = pdfconstant("FileAttachment"),
                FS       = aref,
                Contents = pdfunicode(title),
                Name     = name,
                NM       = pdfstring("attachment:"..nofattachments),
                T        = author ~= "" and pdfunicode(author) or nil,
                Subj     = subtitle ~= "" and pdfunicode(subtitle) or nil,
                C        = analyzecolor(specification.colorvalue,specification.colormodel),
                CA       = analyzetransparency(specification.transparencyvalue),
                AP       = appearance,
                OC       = analyzelayer(specification.layer),
             -- F        = pdfnull(), -- another rediculous need to satisfy validation
                F        = bit32.band(bit32.bor(flags,4),(1023-1-2-32-256)), -- set 3, clear 1,2,6,9; PDF 32000-1, p385
            }
            local width  = specification.width  or 0
            local height = specification.height or 0
            local depth  = specification.depth  or 0
            local box    = hpacknode(nodeinjections.annotation(width,height,depth,d()))
            box.width    = width
            box.height   = height
            box.depth    = depth
            return box
        end
    end
end

function codeinjections.attachmentid(filename) -- not used in context
    return filestreams[filename]
end

-- Comments

local nofcomments      = 0
local usepopupcomments = false

local defaultattributes = {
    ["xmlns"]           = "http://www.w3.org/1999/xhtml",
    ["xmlns:xfa"]       = "http://www.xfa.org/schema/xfa-data/1.0/",
    ["xfa:contentType"] = "text/html",
    ["xfa:APIVersion"]  = "Acrobat:8.0.0",
    ["xfa:spec"]        = "2.4",
}

local function checkcontent(text,option)
    if option and option.xml then
        local root = xml.convert(text)
        if root and not root.er then
            xml.checkbom(root)
            local body = xml.first(root,"/body")
            if body then
                local at = body.at
                for k, v in next, defaultattributes do
                    if not at[k] then
                        at[k] = v
                    end
                end
             -- local content = xml.textonly(root)
                local richcontent = xml.tostring(root)
                return nil, pdfunicode(richcontent)
            end
        end
    end
    return pdfunicode(text)
end

function nodeinjections.comment(specification) -- brrr: seems to be done twice
    nofcomments = nofcomments + 1
    local text = specification.data or ""
    if specification.space ~= v_yes then
        text = stripstring(text)
        text = gsub(text,"[\n\r] *","\n")
    end
    text = gsub(text,"\r","\n")
    local name, appearance = analyzesymbol(specification.symbol,comment_symbols)
    local tag      = specification.tag      or "" -- this is somewhat messy as recent
    local title    = specification.title    or "" -- versions of acrobat see the title
    local subtitle = specification.subtitle or "" -- as author
    local author   = specification.author   or ""
    local option   = settings_to_hash(specification.option or "")
    if author ~= "" then
        if subtitle == "" then
            subtitle = title
        elseif title ~= "" then
            subtitle = subtitle .. ", " .. title
        end
        title = author
    end
    if title == "" then
        title = tag
    end
    local content, richcontent = checkcontent(text,option)
    local d = pdfdictionary {
        Subtype   = pdfconstant("Text"),
        Open      = option[v_max] and pdfboolean(true) or nil,
        Contents  = content,
        RC        = richcontent,
        T         = title ~= "" and pdfunicode(title) or nil,
        Subj      = subtitle ~= "" and pdfunicode(subtitle) or nil,
        C         = analyzecolor(specification.colorvalue,specification.colormodel),
        CA        = analyzetransparency(specification.transparencyvalue),
        OC        = analyzelayer(specification.layer),
        Name      = name,
        NM        = pdfstring("comment:"..nofcomments),
        AP        = appearance,
    }
    local width  = specification.width  or 0
    local height = specification.height or 0
    local depth  = specification.depth  or 0
    local box
    if usepopupcomments then
        -- rather useless as we can hide/vide
        local nd = pdfreserveobject()
        local nc = pdfreserveobject()
        local c = pdfdictionary {
            Subtype = pdfconstant("Popup"),
            Parent  = pdfreference(nd),
        }
        d.Popup = pdfreference(nc)
        box = hpacknode(
            nodeinjections.annotation(0,0,0,d(),nd),
            nodeinjections.annotation(width,height,depth,c(),nc)
        )
    else
        box = hpacknode(nodeinjections.annotation(width,height,depth,d()))
    end
    box.width  = width  -- redundant
    box.height = height -- redundant
    box.depth  = depth  -- redundant
    return box
end

-- rendering stuff
--
-- object_1  -> <</Type /Rendition /S /MR /C << /Type /MediaClip ... >> >>
-- object_2  -> <</Type /Rendition /S /MR /C << /Type /MediaClip ... >> >>
-- rendering -> <</Type /Rendition /S /MS [objref_1 objref_2]>>
--
-- we only work foreward here (currently)
-- annotation is to be packed at the tex end

-- aiff audio/aiff
-- au   audio/basic
-- avi  video/avi
-- mid  audio/midi
-- mov  video/quicktime
-- mp3  audio/x-mp3 (mpeg)
-- mp4  audio/mp4
-- mp4  video/mp4
-- mpeg video/mpeg
-- smil application/smil
-- swf  application/x-shockwave-flash

-- P  media play parameters (evt /BE for controls etc
-- A  boolean (audio)
-- C  boolean (captions)
-- O  boolean (overdubs)
-- S  boolean (subtitles)
-- PL pdfconstant("ADBE_MCI"),

-- F        = flags,
-- T        = title,
-- Contents = rubish,
-- AP       = irrelevant,

-- sound is different, no window (or zero) so we need to collect them and
-- force them if not set

local ms, mu, mf = { }, { }, { }

local function delayed(label)
    local a = pdfreserveobject()
    mu[label] = a
    return pdfreference(a)
end

local function insertrenderingwindow(specification)
    local label = specification.label
 -- local openpage = specification.openpage
 -- local closepage = specification.closepage
    if specification.option == v_auto then
        if openpageaction then
            -- \handlereferenceactions{\v!StartRendering{#2}}
        end
        if closepageaction then
            -- \handlereferenceactions{\v!StopRendering {#2}}
        end
    end
    local actions = nil
    if openpage or closepage then
        actions = pdfdictionary {
            PO = (openpage  and lpdfaction(openpage )) or nil,
            PC = (closepage and lpdfaction(closepage)) or nil,
        }
    end
    local page = tonumber(specification.page) or texgetcount("realpageno") -- todo
    local r = mu[label] or pdfreserveobject() -- why the reserve here?
    local a = pdfdictionary {
        S  = pdfconstant("Rendition"),
        R  = mf[label],
        OP = 0,
        AN = pdfreference(r),
    }
    local bs, bc = pdfborder()
    local d = pdfdictionary {
        Subtype = pdfconstant("Screen"),
        P       = pdfreference(pdfpagereference(page)),
        A       = a, -- needed in order to make the annotation clickable (i.e. don't bark)
        T       = pdfunicode(label), -- for JS
        Border  = bs,
        C       = bc,
        AA      = actions,
    }
    local width = specification.width or 0
    local height = specification.height or 0
    context(nodeinjections.annotation(width,height,0,d(),r)) -- save ref
    return pdfreference(r)
end

-- some dictionaries can have a MH (must honor) or BE (best effort) capsule

local function insertrendering(specification)
    local label  = specification.label
    local option = settings_to_hash(specification.option)
    if not mf[label] then
        local filename = specification.filename
        local isurl    = find(filename,"://",1,true)
        local mimetype = specification.mimetype or specification.mime
     -- local start = pdfdictionary {
     --     Type = pdfconstant("MediaOffset"),
     --     S = pdfconstant("T"), -- time
     --     T = pdfdictionary { -- time
     --         Type = pdfconstant("Timespan"),
     --         S    = pdfconstant("S"),
     --         V    = 3, -- time in seconds
     --     },
     -- }
     -- local start = pdfdictionary {
     --     Type = pdfconstant("MediaOffset"),
     --     S = pdfconstant("F"), -- frame
     --     F = 100 -- framenumber
     -- }
     -- local start = pdfdictionary {
     --     Type = pdfconstant("MediaOffset"),
     --     S = pdfconstant("M"), -- mark
     --     M = "somemark",
     -- }
     -- local parameters = pdfdictionary {
     --     BE = pdfdictionary {
     --          B = start,
     --     }
     -- }
        local parameters = pdfdictionary {
            Type = pdfconstant("MediaPermissions"),
            TF   = pdfstring("TEMPALWAYS"), -- TEMPNEVER TEMPEXTRACT TEMPACCESS TEMPALWAYS / needed for acrobat/wmp
        }
        local descriptor = pdfdictionary {
            Type = pdfconstant("Filespec"),
            F    = filename,
        }
        if isurl then
            descriptor.FS = pdfconstant("URL")
            descriptor = pdfreference(pdfflushobject(descriptor))
        elseif option[v_embed] then
            descriptor = codeinjections.embedfile {
                file           = filename,
                mimetype       = mimetype, -- yes or no
                compress       = option[v_compress] or false,
                forcereference = true,
            }
        end
        local clip = pdfdictionary {
            Type = pdfconstant("MediaClip"),
            S    = pdfconstant("MCD"),
            N    = label,
            CT   = mimetype,
            Alt  = pdfarray { "", "file not found" }, -- language id + message
            D    = descriptor,
            P    = pdfreference(pdfflushobject(parameters)),
        }
        local rendition = pdfdictionary {
            Type = pdfconstant("Rendition"),
            S    = pdfconstant("MR"),
            N    = pdfunicode(label),
            C    = pdfreference(pdfflushobject(clip)),
        }
        mf[label] = pdfreference(pdfflushobject(rendition))
    end
end

local function insertrenderingobject(specification) -- todo
    local label = specification.label
    if not mf[label] then
        report_media("unknown medium, label %a",label)
        local clip = pdfdictionary { -- does  not work that well one level up
            Type = pdfconstant("MediaClip"),
            S    = pdfconstant("MCD"),
            N    = label,
            D    = pdfreference(unknown), -- not label but objectname, hm .. todo?
        }
        local rendition = pdfdictionary {
            Type = pdfconstant("Rendition"),
            S    = pdfconstant("MR"),
            N    = label,
            C    = pdfreference(pdfflushobject(clip)),
        }
        mf[label] = pdfreference(pdfflushobject(rendition))
    end
end

function codeinjections.processrendering(label)
    local specification = interactions.renderings.rendering(label)
    if not specification then
        -- error
    elseif specification.type == "external" then
        insertrendering(specification)
    else
        insertrenderingobject(specification)
    end
end

-- needed mapping for access from JS

local function flushrenderings()
    if next(mf) then
        local r = pdfarray()
        for label, reference in sortedhash(mf) do
            r[#r+1] = pdfunicode(label)
            r[#r+1] = reference -- already a reference
        end
        lpdf.addtonames("Renditions",pdfreference(pdfflushobject(pdfdictionary{ Names = r })))
    end
end

lpdf.registerdocumentfinalizer(flushrenderings,"renderings")

function codeinjections.insertrenderingwindow(specification)
    local label = specification.label
    codeinjections.processrendering(label)
    ms[label] = insertrenderingwindow(specification)
end

local function set(operation,arguments)
    codeinjections.processrendering(arguments)
    return pdfdictionary {
        S  = pdfconstant("Rendition"),
        OP = operation,
        R  = mf[arguments],
        AN = ms[arguments] or delayed(arguments),
    }
end

function executers.startrendering (arguments) return set(0,arguments) end
function executers.stoprendering  (arguments) return set(1,arguments) end
function executers.pauserendering (arguments) return set(2,arguments) end
function executers.resumerendering(arguments) return set(3,arguments) end
