if not modules then modules = { } end modules ['lpdf-lmt'] = {
    version   = 1.001,
    optimize  = true,
    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"
}

-- The code below was originally in back-lpd.lua but it makes more sense in this
-- namespace. I will rename variables. Because we run into the 200 locals this file
-- has to be split.

-- There is no way that a lua based backend can compete performance wise with the
-- original one for relative simple text runs. And we're talking seconds here on say
-- 500 pages with paragraphs alternating between three fonts and colors. But such
-- documents are rare so in practice we are quite okay, especially because in
-- ConTeXt we can gain quite a bit elsewhere. So, when we loose 30% on such simple
-- documents, we break even on for instance the manual, and gain 30% on Thomas's
-- turture test (also for other reasons). But .. who knows what magic I can cook up
-- in due time.

-- If you consider this complex, watch:
--
-- https://www.youtube.com/watch?v=6H-cAzfB2qo
--
-- or in distractionmode:
--
-- https://www.youtube.com/watch?v=TYuTE_1jvvE
-- https://www.youtube.com/watch?v=nnicGKX3lvM
--
-- For the moment we have to support the built in backend as well as the alternative. So
-- the next interface is suboptimal and will change at some time. At that moment I will
-- also optimize and extend.

local type, next, unpack, tonumber, rawget = type, next, unpack, tonumber, rawget
local char, rep, find, gsub = string.char, string.rep, string.find, string.gsub
local formatters = string.formatters
local concat, sortedhash = table.concat, table.sortedhash
local setmetatableindex = table.setmetatableindex
local loaddata = io.loaddata

local bpfactor       <const> = number.dimenfactors.bp

local osuuid                 = os.uuid
local zlibcompresssize       = xzip.compresssize

local nuts                   = nodes.nuts

local pdfreference           = lpdf.reference
local pdfdictionary          = lpdf.dictionary
local pdfarray               = lpdf.array
local pdfconstant            = lpdf.constant
local pdfliteral             = lpdf.literal -- not to be confused with a whatsit!
local pdfunicode             = lpdf.unicode

local pdfreserveobject       -- forward reference
local pdfpagereference       -- forward reference
local pdfgetpagereference    -- forward reference
local pdfsharedobject        -- forward reference
local pdfflushobject         -- forward reference
local pdfflushstreamobject   -- forward reference
local pdfdeferredobject      -- forward reference
local pdfimmediateobject     -- forward reference

local pdfincludeimage        -- forward reference

local pdf_pages         = pdfconstant("Pages")
local pdf_page          = pdfconstant("Page")
local pdf_xobject       = pdfconstant("XObject")
local pdf_form          = pdfconstant("Form")
local pdf_pattern       = pdfconstant("Pattern")

local fonthashes        = fonts.hashes
local characters        = fonthashes.characters
local descriptions      = fonthashes.descriptions
local parameters        = fonthashes.parameters
local properties        = fonthashes.properties

local report            = logs.reporter("backend")
local report_objects    = logs.reporter("backend","objects")
local report_fonts      = logs.reporter("backend","fonts")
local report_encryption = logs.reporter("backend","encryption")

local trace_objects     = false  trackers.register("backend.objects",       function(v) trace_objects     = v end)
local trace_details     = false  trackers.register("backend.details",       function(v) trace_details     = v end)
local trace_indices     = false  trackers.register("backend.fonts.details", function(v) trace_indices     = v end)
local trace_compression = false  trackers.register("backend.compression",   function(v) trace_compression = v end)

-- These two tables used a font id as index and will be metatabled in lpdf-emb.lmt:

local usedfontnames     = { }
local usedfontobjects   = { }

lpdf.usedfontnames      = usedfontnames
lpdf.usedfontobjects    = usedfontobjects

-- What is actually the minimum size, we don't need to test when less than that. An
-- empty string gives 8 bytes, we also need "/Length 123" plus some space and we
-- likely have little to compress on small string, so say 60 bytes at least. With
-- object compression we never hit the threshold.

-- local threshold <const> = 64

local function compressdata(data,size)
 -- if size > threshold then
        local guess = ((size // 4096) + 1) * 2048
        local comp  = zlibcompresssize(data,guess,3)
        if trace_compression and comp then
            local csize = #comp
            report()
            report("size %i, guess %i, result %i, guessed %a, needed %a",
                size, guess, csize,
                guess >= csize and "good" or "bad",
                csize <  size  and "yes"  or "no"
            )
            report()
        end
        return comp
 -- else
 --     if trace_compression and comp then
 --         report()
 --         report("size %i, threshold %i, needed %a",
 --             size, threshold, "no"
 --         )
 --         report()
 --     end
 --     return nil
 -- end
end

-- we collect them:

local flushers = { }

-- used variables

local pdf_h = 0
local pdf_v = 0

local need_tm
local need_tf
local need_font
----- cur_tmrx
local need_width
local need_mode
local done_width
local done_mode
local mode
local current_pdf
----- f_pdf_cur
local current_font
local current_effect
local current_slant
local current_weight
local current_sx
local current_sy
local current_factor
local f_x_scale
local f_y_scale
local tj_scale
local tj_delta
local tj_position
----- tmrx, tmry, tmsx, tmsy, tmtx, tmty
local tmrx, tmry,       tmsy, tmtx, tmty
----- cmrx, cmry, cmsx, cmsy, cmtx, cmty
local cmrx, cmry,             cmtx, cmty
local tmef
local tmef_f_x_scale
local tmef_f_w_scale

local usedfonts, usedxforms, usedximages, usedxgroups
local getxformname, getximagename
local boundingbox, shippingmode, objectnumber

local function usefont(t,k) -- a bit redundant hash
 -- local v = pdfgetfontname(k)
    local v = usedfontnames[k]
    t[k] = v
    return v
end

local function reset_variables(specification)
    pdf_h, pdf_v   = 0, 0
    cmrx, cmry     = 1.0, 1.0
 -- cmsx, cmsy     = 0.0, 0.0
    cmtx, cmty     = 0.0, 0.0
    tmrx, tmry     = 1.0, 1.0
 -- tmsx, tmsy     = 0.0, 0.0
          tmsy     =      0.0 -- sy makes no sense
    tmtx, tmty     = 0.0, 0.0
    tmef           = 1.0
    need_tm        = false
    need_tf        = false
    need_font      = true
    need_width     = 0
    need_mode      = 0
    done_width     = false
    done_mode      = false
    mode           = "page"
    shippingmode   = specification.shippingmode
    objectnumber   = specification.objectnumber
 -- cur_tmrx       = 0.0
 -- f_pdf_cur      = 0 -- nullfont
    tj_scale       = 1
    tj_delta       = 0.0
    tj_position    = 0.0
    current_font   = 0
    current_pdf    = 0 -- nullfont
    current_effect = nil
    current_slant  = 0
    current_weight = 0
    current_factor = 0
    current_sx     = 1
    current_sy     = 1
    f_x_scale      = 1.0
    f_y_scale      = 1.0
    tmef_f_x_scale = 1
    tmef_f_w_scale = 1
    usedfonts      = setmetatableindex(usefont)
    usedxforms     = { }
    usedximages    = { }
 -- usedxgroups    = { }
    boundingbox    = specification.boundingbox
end

-- buffer

local buffer = lua.newtable(1024,0) -- { }
local b      = 0

local function reset_buffer()
    b = 0
end

-- fonts

-- The text in a PDF file ends up in the page stream. Right from the start PDF files
-- were supposed to be efficient but over time that concept was messed up by for
-- instance mixed in tagging. One can argue if that was a good idea: just embed the
-- source and use that as for special purposes instead of trying to turn something
-- visual into something structure. We have these angle bracket formats for that
-- already. Anyway, here we assume an uninterupted stream.
--
-- A sequence of characters in the same font and with the same scale (and other
-- rendering properties). When we started with the backend in Lua I just followed
-- the same approach as in LuaTeX (which was modelled after pdfTeX) but at some
-- point a (stepwise) transition took place. For instance, we always use a 10bp font
-- size (instead of the scale in the document) and delegate all scaling to the font
-- transform matrix. Because the text stream uses a different coordinate system
-- (font units combined with 1000 based kerning) we need to keep track of where we
-- are and when we drift too much we need to restart. In the engines this is
-- complicated by integer math so there drift really plays a role, but in our case
-- we can now approach it a bit different, although we don't really know the
-- acceptable thresholds. This code evolved over time, also because we introduced
-- additional scaling options.
--
-- So, in the backend we have to deal with this:
--
-- - positioning in target space (page)
-- - positioning in the stream (text)
-- - glyph scale (!), glyph xscale (!), glyph yscale
-- - font scale (!)
-- - horizontal extend (!)
-- - vertical squeeze
-- - slant
-- - expansion factor (!)
-- - font effects (mode and width/line)
-- - temporary glyph effects (extend, squeeze, slant, mode, width/line)
-- - drift (synchronization and check for threshold)
--
-- This means that we have to check if one of these properties has changed and the
-- horizontal scale is the most demanding one. We also need to mix the check for
-- font specific changes and glyph specific effects. For now we assume that slanted
-- font is just a copy but in the future we could just make it a glyph property like
-- scale, xscale and yscale. An even more advanced approach is to integrate
-- boldening but then we also need 4 offsets and runtime calculation, while it might
-- also complicate e.g. extensibles in math. There is much more overhead so it will
-- also impact performance.

local fontcharacters
----- fontdescriptions
local fontparameters
local fontproperties
local pdfcharacters

local getstreamhash = fonts.handlers.otf.getstreamhash

local usedfontstreams = utilities.storage.allocate()

local usedindices = setmetatableindex(function(t,k)
    local n = 0
-- n = 31
    local v = setmetatableindex(function(tt,kk)
        if n >= 0xFFFF then
            report_fonts("registering character index: overflow in hash %a, todo: use overflow font")
        else
            n = n + 1
        end
        if trace_indices then
            report_fonts("registering character index: hash %a, charindex 0x%05X, slotindex 0x%04X",k,kk,n)
        end
        local vv = n
        tt[kk] = vv
        return vv
    end)
    t[k] = v
    return v
end)

local usedcharacters = setmetatableindex(function(t,k)
    local h, d = getstreamhash(k)
    if trace_indices then
        report_fonts("registering index table: hash %a, fontid %i",h,k)
    end
    usedfontstreams[h] = d
    local v = usedindices[h]
    t[k] = v
    return v
end)

lpdf.usedfontstreams = usedfontstreams -- [streamhash]        -> fontdata
lpdf.usedcharacters  = usedcharacters  -- [fontid]            -> indices
lpdf.usedindices     = usedindices     -- [streamhash][index] -> realindex (can also be dupindex)

local horizontalmode = true
local scalefactor    = 1
local threshold      = 655360
local tjfactor       = 100 / 65536

function flushers.updatefontstate(font)
    -- virtual t3 fonts have negative font index
    fontcharacters   = characters[font]
 -- fontdescriptions = descriptions[font]
    fontparameters   = parameters[font]
    fontproperties   = properties[font]
    local size       = fontparameters.size -- or bad news
    local designsize = fontparameters.designsize or size
    pdfcharacters    = usedcharacters[font]
    horizontalmode   = fontparameters.writingmode ~= "vertical"
    scalefactor      = (designsize/size) * tjfactor
end

local f_cm = formatters["%.6N %.6N %.6N %.6N %.6N %.6N cm"]
local f_cz = formatters["%.6N 0 0 %.6N %.6N %.6N cm"]
----- f_tm = formatters["%.6N %.6N %.6N %.6N %.6N %.6N Tm"]
local f_tm = formatters["%.6N 0 %.6N %.6N %.6N %.6N Tm"]

do
    local t = { true, "0", true, true, true, true, "Tm" }
    local f = string.f6
    local h = setmetatableindex(function(t,k) local v = f(k) t[k] = v return v end)
    f_tm = function(rx, sy, ry, tx, ty)
        if rx == ry then
            rx = h[rx]
            t[1] = rx
            t[4] = rx
        else
            t[1] = h[rx]
            t[4] = h[ry]
        end
        if sy == 0 then
            t[3] = "0"
        else
         -- t[3] = f(sy)
            t[3] = h[sy]
        end
        t[5] = f(tx)
        t[6] = f(ty)
        return concat(t," ")
    end
end

directives.register("backend.pdf.accurate", function()
    f_cm = formatters["%.9N %.9N %.9N %.9N %.9N %.9N cm"]
    f_cz = formatters["%.9N 0 0 %.9N %.9N %.9N cm"]
 -- f_tm = formatters["%.9N %.9N %.9N %.9N %.9N %.9N Tm"]
    f_tm = formatters["%.9N 0 %.9N %.9N %.9N %.9N Tm"]
end)

local saved_text_pos_v = 0
local saved_text_pos_h = 0

local function begin_text()
    saved_text_pos_h = pdf_h
    saved_text_pos_v = pdf_v
    b = b + 1 ; buffer[b] = "BT"
    need_tf        = true
    need_font      = true
    need_width     = 0
    need_mode      = 0
    current_effect = nil
    current_slant  = 0
    current_weight = 0
    mode           = "text"
end

local function end_text()
    if done_width then
        b = b + 1 ; buffer[b] = "0 w"
        done_width = false
    end
    if done_mode then
        b = b + 1 ; buffer[b] = "0 Tr"
        done_mode = false
    end
    b = b + 1 ; buffer[b] = "ET"
    pdf_h = saved_text_pos_h
    pdf_v = saved_text_pos_v
    mode  = "page"
end

local begin_chararray, end_chararray do

    local saved_chararray_pos_h
    local saved_chararray_pos_v

    local saved_b = 0

    begin_chararray = function()
        saved_chararray_pos_h = pdf_h
        saved_chararray_pos_v = pdf_v
        tj_position = horizontalmode and saved_chararray_pos_h or - saved_chararray_pos_v
        tj_delta = 0
        saved_b = b
        b = b + 1 ; buffer[b] = " ["
        mode = "chararray"
    end

    end_chararray = function()
        b = b + 1 ; buffer[b] = "] TJ"
        buffer[saved_b] = concat(buffer,"",saved_b,b)
        b = saved_b
        pdf_h = saved_chararray_pos_h
        pdf_v = saved_chararray_pos_v
        mode  = "text"
    end

end

local function begin_charmode()
    b = b + 1 ; buffer[b] = "<"
    mode = "char"
end

local function end_charmode()
    b = b + 1 ; buffer[b] = ">"
    mode = "chararray"
end

local function calc_pdfpos(h,v)
    -- mostly char
    if mode == "page" then
        cmtx = h - pdf_h
        cmty = v - pdf_v
        return h ~= pdf_h or v ~= pdf_v
    elseif mode == "text" then
        tmtx = h - saved_text_pos_h
        tmty = v - saved_text_pos_v
        return h ~= pdf_h or v ~= pdf_v
    elseif horizontalmode then
        tmty = v - saved_text_pos_v
        tj_delta = tj_position - h
        return tj_delta ~= 0 or v ~= pdf_v
    else
        tmtx = h - saved_text_pos_h
        tj_delta = tj_position + v
        return tj_delta ~= 0 or h ~= pdf_h
    end
end

local function pdf_set_pos(h,v)
    local move = calc_pdfpos(h,v)
    if move then
     -- b = b + 1 ; buffer[b] = f_cm(cmrx, cmsx, cmsy, cmry, cmtx*bpfactor, cmty*bpfactor)
        b = b + 1 ; buffer[b] = f_cz(cmrx, cmry,             cmtx*bpfactor, cmty*bpfactor)
        pdf_h = pdf_h + cmtx
        pdf_v = pdf_v + cmty
    end
end

local function pdf_reset_pos()
    if mode == "page" then
        cmtx = - pdf_h
        cmty = - pdf_v
        if pdf_h == 0 and pdf_v == 0 then
            return
        end
    elseif mode == "text" then
        tmtx = - saved_text_pos_h
        tmty = - saved_text_pos_v
        if pdf_h == 0 and pdf_v == 0 then
            return
        end
    elseif horizontalmode then
        tmty = - saved_text_pos_v
        tj_delta = tj_position
        if tj_delta == 0 and pdf_v == 0 then
            return
        end
    else
        tmtx = - saved_text_pos_h
        tj_delta = tj_position
        if tj_delta == 0 and pdf_h == 0 then
            return
        end
    end
 -- b = b + 1 ; buffer[b] = f_cm(cmrx, cmsx, cmsy, cmry, cmtx*bpfactor, cmty*bpfactor)
    b = b + 1 ; buffer[b] = f_cz(cmrx,             cmry, cmtx*bpfactor, cmty*bpfactor)
    pdf_h = pdf_h + cmtx
    pdf_v = pdf_v + cmty
end

local function pdf_set_pos_temp(h,v)
    local move = calc_pdfpos(h,v)
    if move then
     -- b = b + 1 ; buffer[b] = f_cm(cmrx, cmsx, cmsy, cmry, cmtx*bpfactor, cmty*bpfactor)
        b = b + 1 ; buffer[b] = f_cz(cmrx,             cmry, cmtx*bpfactor, cmty*bpfactor)
    end
end

-- these dummy returns makes using them a bit faster

local function pdf_end_string_nl()
    if mode == "char" then
        end_charmode()
        return end_chararray()
    elseif mode == "chararray" then
        return end_chararray()
    end
end

local function pdf_goto_textmode()
    if mode == "page" then
        pdf_reset_pos()
        return begin_text()
    elseif mode ~= "text" then
        if mode == "char" then
            end_charmode()
            return end_chararray()
        else -- if mode == "chararray" then
            return end_chararray()
        end
    end
end

local function pdf_goto_pagemode()
    if mode ~= "page" then
        if mode == "char" then
            end_charmode()
            end_chararray()
            return end_text()
        elseif mode == "chararray" then
            end_chararray()
            return end_text()
        elseif mode == "text" then
            return end_text()
        end
    end
end

local function pdf_goto_fontmode()
    if mode == "char" then
        end_charmode()
        end_chararray()
        end_text()
    elseif mode == "chararray" then
        end_chararray()
        end_text()
    elseif mode == "text" then
        end_text()
    end
    pdf_reset_pos()
    mode = "page"
end

-- characters

-- we could actually do this:
--
-- -- effactor (expand) -> into glyphxscale
-- -- extend            -> into glyphxscale
-- -- squeeze           -> into glyphyscale

do

    local round = math.round

    -- across pages ... todo: clean up because we don't need to pass the font
    -- as fontparameters already has checked / set it we can also have a variable
    -- for it so

    local characterwidth   = nil
 -- local descriptionwidth = nil
    local hshift           = false
    local vshift           = false

    local dupx = 100 / bpfactor -- 6578176.0
    local dumx = - dupx
    local dupy = dupx
    local dumy = dumx

    directives.register("backend.pdf.drift", function(v)
        dupx = tonumber(v) or 100
        if dupx < 10 then
            dupx = 10
        elseif dupx > 100 then
            dupx = 100
        end
        dupx = dupx / bpfactor
    end)

    local h_hex_2 = lpdf.h_hex_2
    local h_hex_4 = lpdf.h_hex_4

    -- The width array uses the original dimensions! This is different from e.g.
    -- luatex where we have more widths arrays and these reflect the cheated
    -- widths (goes wrong elsewhere).

    -- when changing this, check math: compact-001.tex (rule width)

    local characterwidths = setmetatableindex(function(t,font)
        local d = descriptions[font]
        local c = characters[font]
        local f = parameters[font].hfactor or parameters[font].factor
        local v = setmetatableindex(function(t,char)
            local w
            local e = c and c[char]
            if e then
                local a = e.advance
                if a then
                    w = a
                else
                    w = e.width or 0
                end
            end
            if not w then
                e = d and d[char]
                if e then
                    w = e.width
                    if w then
                        w =  w * f
                    end
                end
            end
            if not w then
                w = 0
            end
            t[char] = w
            return w
        end)
        t[font] = v
        return v
    end)

    -- the descriptions are used for the width array

    local extend  = 1 -- some more can move here
    local squeeze = 1
    local tohex   = h_hex_4

    local function setup_fontparameters(font,factor,sx,sy,slant,weight,effect)
        --
        current_sx = sx
        current_sy = sy
        --
        if fontproperties.bitmapped then
            tohex = h_hex_2
        elseif font < 0 then
            tohex = h_hex_2
        else
            tohex = h_hex_4
        end

        local format = fontproperties.format
        local expand = 1 + factor / 1000000
        if effect then
            -- We have glyph specific effects and these have a higher priority than
            -- the font specific effects.
            if effect ~= current_effect then
                current_effect   = effect
                tmrx             = 1
                tmry             = 1
                tmsy             = effect.slant   or fontparameters.slantfactor   or slant or 0
                extend           = effect.extend  or fontparameters.extendfactor  or 1
                squeeze          = effect.squeeze or fontparameters.squeezefactor or 1
                need_mode        = effect.mode    or fontparameters.mode          or 0
                need_width       = effect.weight  or fontparameters.weight        or 0
                sx               = extend  * sx
                sy               = squeeze * sy
            else
             -- we could check if effects have changed but effects use unique tables; for
             -- now they win over font effects (only used in math)
tmrx             = 1
tmry             = 1
-- tmsy             = effect.slant   or fontparameters.slantfactor   or slant or 0
            end
        else
            -- These are the font specific effects and the scales are part of the main
            -- font setup, so we use the factors and not the properties.effect data.
            current_effect = nil
            tmrx           = 1
            tmry           = 1
            --
            local e = fontproperties.effect
            if e then
                tmsy       = e.slant   or slant or 0
                extend     = e.extend  or 1
                squeeze    = e.squeeze or 1
                need_mode  = e.mode    or 0
                need_width = e.weight  or 0
                sx         = extend  * sx
                sy         = squeeze * sy
            else
                tmsy       = slant or 0
                extend     = 1
                squeeze    = 1
                need_mode  = 0
                need_width = weight
            end
        end

        tmef = expand
        tmrx = expand * tmrx

        current_font   = font
        current_pdf    = usedfonts[font] -- cache
        current_factor = factor
        current_slant  = tmsy
        current_weight = weight

        local sc = fontparameters.size * bpfactor / 10
        -- kind of special:
        if format == "opentype" or format == "type1" then
            sc = sc * 1000 / fontparameters.units
            tj_scale = fontparameters.units / 1000
        else
            tj_scale = 1
        end
        tj_delta    = 0
        tj_position = 0
        --
        tmsy = tmsy * sc
        tmrx = tmrx * sc
        tmry = tmry * sc
        --
        f_x_scale = sx
        if f_x_scale ~= 1.0 then
            tmrx = tmrx * f_x_scale
        end
        f_y_scale = sy
        if f_y_scale ~= 1.0 then
            tmsy = tmsy * f_y_scale
            tmry = tmry * f_y_scale
        end
        --
        tmef_f_x_scale = tmef * f_x_scale
        --
        if tmef_f_x_scale == 1.0 then
            tmef_f_x_scale = 1
        end
        --
        tj_scale = tj_scale * scalefactor / tmef_f_x_scale
        --
        tmef_f_w_scale = tmef_f_x_scale / extend
        --
        characterwidth   = characterwidths[font]
     -- descriptionwidth = descriptionwidths[font]
        --
        hshift = fontparameters.hshift
        vshift = fontparameters.vshift

        if need_width > 0 then
            if not need_mode or need_mode == 0 then
                need_mode = 2
            end
            need_width = need_width * 2
            need_width = need_width * tmrx
        end

    end

    local f_width = formatters["%.6N w"]
    local f_mode  = formatters["%i Tr"]        -- can be hash
    local f_font  = formatters["/F%i 10 Tf"]
    local f_skip  = formatters["%.2N"]

    local h_font = setmetatableindex(function(t,k) local v = f_font(k) t[k] = v return v end)
    local h_skip = setmetatableindex(function(t,k) local v = f_skip(k) t[k] = v return v end)

    local s_width = "0 w"
    local s_mode  = "0 Tr"

    local width_factor = 72.27 / 72.0

    local last_pdf

    local function set_font()
     -- if need_width and need_width ~= 0 then
        if need_width ~= 0 then
            b = b + 1 ; buffer[b] = f_width(width_factor*need_width)
            done_width = true
        elseif done_width then
            b = b + 1 ; buffer[b] = s_width
            done_width = false
        end
     -- if need_mode and need_mode ~= 0 then
        if need_mode ~= 0 then
            b = b + 1 ; buffer[b] = f_mode(need_mode)
            done_mode = true
        elseif done_mode then
            b = b + 1 ; buffer[b] = s_mode
            done_mode = false
        end
        -- no need when the same
        if need_font or last_pdf ~= current_pdf then
--             b = b + 1 ; buffer[b] = f_font(current_pdf)
            b = b + 1 ; buffer[b] = h_font[current_pdf]
            last_pdf  = current_pdf
            need_font = false
        end
     -- f_pdf_cur = current_pdf
        need_tf = false
        need_tm = true
    end

    local function set_textmatrix(h,v)
       local move = calc_pdfpos(h,v)
       if need_tm or move then
         -- b = b + 1 ; buffer[b] = f_tm(tmrx, tmsx, tmsy, tmry, tmtx*bpfactor, tmty*bpfactor)
            b = b + 1 ; buffer[b] = f_tm(tmrx,       tmsy, tmry, tmtx*bpfactor, tmty*bpfactor)
            pdf_h = saved_text_pos_h + tmtx
            pdf_v = saved_text_pos_v + tmty
            need_tm = false
        end
     -- cur_tmrx = tmrx
    end

    -- I will redo this mess ... we no longer have the mkiv pdf generator that we used in
    -- luatex (a precursor to lmtx and also for comparison) but only in lmtx now so ...
    -- time to move on I guess.

    -- factor is for hz

    flushers.character = function(current,pos_h,pos_v,pos_r,font,char,data,csx,csy,factor,sx,sy,slant,weight) -- ,naturalwidth,width)

        local s = data.scale
        local x = data.xoffset
        local y = data.yoffset
        local effect = data.effect

        if s then
            sx = s * sx
            sy = s * sy
        end
        if csx then
            sx = sx * csx
            csx = 1
        end
        if csy then
            sy = sy * csy
            csy = 1
        end
        if     sx ~= current_sx -- was: f_x_scale
            or sy ~= current_sy -- was: f_y_scale
            or need_tf
            or font ~= current_font
         -- or current_pdf ~= f_pdf_cur
            or mode == "page"
            or slant  ~= current_slant
            or weight ~= current_weight
            or effect ~= current_effect
        then
            pdf_goto_textmode()
            setup_fontparameters(font,factor,sx,sy,slant,weight,effect) -- too often due to page
            set_font()
     -- elseif mode == "page" then
     --     pdf_goto_textmode()
     --     set_font()
        elseif current_factor ~= factor then -- or cur_tmrx ~= tmrx
            -- check if this happens and when
            setup_fontparameters(font,factor,sx,sy,slant,weight,effect)
            need_tm = true
        end

        if x then
            pos_h = pos_h + x * tmef_f_x_scale
        end
        if y then
            pos_v = pos_v + y * f_y_scale
        end

        local move = calc_pdfpos(pos_h,pos_v)

        if move or need_tm then
            if not need_tm then
                if horizontalmode then
                    if (saved_text_pos_v + tmty) ~= pdf_v then
                        need_tm = true
                    elseif tj_delta >= dupx or tj_delta <= dumx then
                        need_tm = true
                    end
                else
                    if (saved_text_pos_h + tmtx) ~= pdf_h then
                        need_tm = true
                    elseif tj_delta >= dupy or tj_delta <= dumy then
                        need_tm = true
                    end
                end
            end
            if hshift then pos_h = pos_h + hshift end
            if vshift then pos_v = pos_v - vshift end
            if need_tm then
                pdf_goto_textmode()
                set_textmatrix(pos_h,pos_v)
                begin_chararray()
                move = calc_pdfpos(pos_h,pos_v)
            end
            if move then
                local d = tj_delta * tj_scale
                if d <= -0.5 or d >= 0.5 then
                    if mode == "char" then
                        end_charmode()
                    end
                    -- ok for text with not too long words where spaces synchronize
                 -- b = b + 1 ; buffer[b] = round(d) -- or f_skip(d)
                    -- better for extensibles and incredible long words
--                     b = b + 1 ; buffer[b] = f_skip(d)
                    b = b + 1 ; buffer[b] = h_skip[d]
                end
                tj_position = tj_position - tj_delta
             -- tj_position = tj_position - tj_delta * tmef_f_x_scale
            end
        end

        if mode == "chararray" then
            begin_charmode()
        end

        -- When we have say 100 x without spaces we do accumulate some error but in practice
        -- we have spaces so we sync. This is the same as in pdftex and luatex.

        tj_position = tj_position + characterwidth[char] * tmef_f_w_scale

        local slot = pdfcharacters[data.index or char] -- registers usage

--         b = b + 1 ; buffer[b] = font > 0 and h_hex_4[slot] or h_hex_2[slot]
        b = b + 1 ; buffer[b] = tohex[slot]

    end

    do

        -- We like a little bit of optimization, just for the fun of it.

        local spaces = setmetatableindex(function(t,font)
            local bytes = false
            local data  = characters[font]
            if data then
                data = data[32]
                if data then
                    local index = data.index
                    if index then
                        local dummy = usedfonts[font]
                        local slot  = usedcharacters[font][index]
                        if slot then
--                             bytes = font > 0 and h_hex_4[slot] or h_hex_2[slot]
                            bytes = tohex[slot]
                        end
                    end
                end
            end
            t[font] = bytes
            return bytes
        end)

        -- We have to be in the current font stream.

        flushers.space = function(font)
            if mode == "char" and font == current_font then
                local slot = spaces[font]
                if slot then
                    tj_position = tj_position + characterwidth[32] * tmef_f_w_scale
                    b = b + 1 ; buffer[b] = slot
                end
            end
        end

    end

    flushers.fontchar = function(font,char,data)
        local dummy = usedfonts[font]
        local slot  = pdfcharacters[data.index or char] -- registers usage
        return dummy, slot
    end

end

-- literals

local flushliteral  do

    local nodeproperties      = nodes.properties.data
    local literalvalues       = nodes.literalvalues

    local originliteral_code  <const> = literalvalues.origin
    local pageliteral_code    <const> = literalvalues.page
    local alwaysliteral_code  <const> = literalvalues.always
    local rawliteral_code     <const> = literalvalues.raw
    local textliteral_code    <const> = literalvalues.text
    local fontliteral_code    <const> = literalvalues.font

    flushliteral = function(current,pos_h,pos_v)
        local p = nodeproperties[current]
        if p then
            local str = p.data
            if str and str ~= "" then
                local mode = p.mode
                if mode == originliteral_code then
                    pdf_goto_pagemode()
                    pdf_set_pos(pos_h,pos_v)
                elseif mode == pageliteral_code then
                    pdf_goto_pagemode()
                elseif mode == textliteral_code then
                    pdf_goto_textmode()
                elseif mode == fontliteral_code then
                    pdf_goto_fontmode()
                elseif mode == alwaysliteral_code then -- aka direct
                    pdf_end_string_nl()
                    need_tm = true
                elseif mode == rawliteral_code then
                    pdf_end_string_nl()
                else
                    report("invalid literal mode %a when flushing %a",mode,str)
                    return
                end
                b = b + 1 ; buffer[b] = str
            end
        end
    end

    flushers.literal = flushliteral

    function lpdf.print(mode,str)
        -- This only works inside objects, don't change this to flush
        -- in between. It's different from luatex but okay.
        if str then
            mode = literalvalues[mode]
        else
            mode, str = originliteral_code, mode
        end
        if str and str ~= "" then
            if mode == originliteral_code then
                pdf_goto_pagemode()
             -- pdf_set_pos(pdf_h,pdf_v)
            elseif mode == pageliteral_code then
                pdf_goto_pagemode()
            elseif mode == textliteral_code then
                pdf_goto_textmode()
            elseif mode == fontliteral_code then
                pdf_goto_fontmode()
            elseif mode == alwaysliteral_code then
                pdf_end_string_nl()
                need_tm = true
            elseif mode == rawliteral_code then
                pdf_end_string_nl()
            else
                report("invalid literal mode %a when flushing %a",mode,str)
                return
            end
            b = b + 1 ; buffer[b] = str
        end
    end

end

-- grouping & orientation

do

    local matrices     = { }
    local positions    = { }
    local nofpositions = 0
    local nofmatrices  = 0

    local flushsave = function(current,pos_h,pos_v)
        nofpositions = nofpositions + 1
        positions[nofpositions] = { pos_h, pos_v, nofmatrices }
        pdf_goto_pagemode()
        pdf_set_pos(pos_h,pos_v)
        b = b + 1 ; buffer[b] = "q"
    end

    local flushrestore = function(current,pos_h,pos_v)
        if nofpositions < 1 then
            return
        end
        local t = positions[nofpositions]
     -- local h = pos_h - t[1]
     -- local v = pos_v - t[2]
        if shippingmode == "page" then
            nofmatrices = t[3]
        end
        pdf_goto_pagemode()
        pdf_set_pos(pos_h,pos_v)
        b = b + 1 ; buffer[b] = "Q"
        nofpositions = nofpositions - 1
    end

    local nodeproperties = nodes.properties.data

    local s_matrix_0 <const> = "1 0 0 1 0 0 cm"
    local f_matrix_2         = formatters["%.6N 0 0 %.6N 0 0 cm"]
    local f_matrix_4         = formatters["%.6N %.6N %.6N %.6N 0 0 cm"]

    local flushsetmatrix = function(current,pos_h,pos_v)
        local p = nodeproperties[current]
        if p then
            local m = p.matrix
            if m then
                local rx, sx, sy, ry = unpack(m)
                local s
                if not rx then
                    rx = 1
                elseif rx == 0 then
                    rx = 0.0001
                end
                if not ry then
                    ry = 1
                elseif ry == 0 then
                    ry = 0.0001
                end
                if not sx then
                    sx = 0
                end
                if not sy then
                    sy = 0
                end
                --
                if sx == 0 and sy == 0 then
                    if rx == 1 and ry == 1 then
                        s = s_matrix_0
                    else
                        s = f_matrix_2(rx,ry)
                    end
                else
                    s = f_matrix_4(rx,sx,sy,ry)
                end
                --
                if shippingmode == "page" then
                    local tx = pos_h * (1 - rx) - pos_v * sy
                    local ty = pos_v * (1 - ry) - pos_h * sx
                    if nofmatrices > 0 then
                        local t = matrices[nofmatrices]
                        local r_x, s_x, s_y, r_y, te, tf = t[1], t[2], t[3], t[4], t[5], t[6]
                        rx, sx = rx * r_x + sx * s_y, rx * s_x + sx * r_y
                        sy, ry = sy * r_x + ry * s_y, sy * s_x + ry * r_y
                        tx, ty = tx * r_x + ty * s_y, tx * s_x + ty * r_y
                    end
                    nofmatrices = nofmatrices + 1
                    matrices[nofmatrices] = { rx, sx, sy, ry, tx, ty, pos_h, pos_v }
                end
                --
                pdf_goto_pagemode()
                pdf_set_pos(pos_h,pos_v)
                --
                b = b + 1
                buffer[b] = s
            end
        end
    end

    flushers.setmatrix = flushsetmatrix
    flushers.save      = flushsave
    flushers.restore   = flushrestore

    function lpdf.hasmatrix()
        return nofmatrices > 0
    end

    function lpdf.getmatrix()
        if nofmatrices > 0 then
            return unpack(matrices[nofmatrices])
        else
            return 1, 0, 0, 1, 0, 0, 0, 0
        end
    end

    flushers.pushorientation = function(orientation,pos_h,pos_v,pos_r)
        pdf_goto_pagemode()
        pdf_set_pos(pos_h,pos_v)
        b = b + 1 ; buffer[b] = "q"
        if orientation == 1 then
            b = b + 1 ; buffer[b] = "0 -1 1 0 0 0 cm"  --  90
        elseif orientation == 2 then
            b = b + 1 ; buffer[b] = "-1 0 0 -1 0 0 cm" -- 180
        elseif orientation == 3 then
            b = b + 1 ; buffer[b] = "0 1 -1 0 0 0 cm"  -- 270
        end
    end

    flushers.poporientation = function(orientation,pos_h,pos_v,pos_r)
        pdf_goto_pagemode()
        pdf_set_pos(pos_h,pos_v)
        b = b + 1 ; buffer[b] = "Q"
    end

    flushers.pushgroup = function()
        pdf_goto_pagemode()
     -- b = b + 1 ; buffer[b] = "% push"
        b = b + 1 ; buffer[b] = "q"
    end

    flushers.popgroup = function()
        pdf_goto_pagemode()
        b = b + 1 ; buffer[b] = "Q"
     -- b = b + 1 ; buffer[b] = "% pop"
    end

    flushers.startmatrix = function(current,pos_h,pos_v)
        flushsave(current,pos_h,pos_v)
        flushsetmatrix(current,pos_h,pos_v)
    end

    flushers.stopmatrix = function(current,pos_h,pos_v)
        flushrestore(current,pos_h,pos_v)
    end

    flushers.startscaling = function(current,pos_h,pos_v)
        flushsave(current,pos_h,pos_v)
        flushsetmatrix(current,pos_h,pos_v)
    end

    flushers.stopscaling = function(current,pos_h,pos_v)
        flushrestore(current,pos_h,pos_v)
    end

    flushers.startrotation = function(current,pos_h,pos_v)
        flushsave(current,pos_h,pos_v)
        flushsetmatrix(current,pos_h,pos_v)
    end

    flushers.stoprotation = function(current,pos_h,pos_v)
        flushrestore(current,pos_h,pos_v)
    end

    flushers.startmirroring = function(current,pos_h,pos_v)
        flushsave(current,pos_h,pos_v)
        flushsetmatrix(current,pos_h,pos_v)
    end

    flushers.stopmirroring = function(current,pos_h,pos_v)
        flushrestore(current,pos_h,pos_v)
    end

    flushers.startclipping = function(current,pos_h,pos_v)
        flushsave(current,pos_h,pos_v)
     -- lpdf.print("origin",formatters["0 w %s W n"](nodeproperties[current].path))
        pdf_goto_pagemode()
        b = b + 1 ; buffer[b] = formatters["0 w %s W n"](nodeproperties[current].path)
    end

    flushers.stopclipping = function(current,pos_h,pos_v)
        flushrestore(current,pos_h,pos_v)
    end

end

do

    local nodeproperties = nodes.properties.data

    flushers.setstate = function(current,pos_h,pos_v)
        local p = nodeproperties[current]
        if p then
            local d = p.data
            if d and d ~= "" then
                pdf_goto_pagemode()
                b = b + 1 ; buffer[b] = d
            end
        end
    end

end

-- rules

local flushedxforms  = { } -- actually box resources but can also be direct
local localconverter = nil -- will be set

do

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

    local pdfbackend        = backends.registered.pdf
    local nodeinjections    = pdfbackend.nodeinjections
    local codeinjections    = pdfbackend.codeinjections

    local newimagerule      = nuts.pool.imagerule
    local newboxrule        = nuts.pool.boxrule

    local setprop           = nuts.setprop
    local getprop           = nuts.getprop
    local setattrlist       = nuts.setattrlist
    local getoptions        = nuts.getoptions

    local getwhd            = nuts.getwhd
    local flushlist         = nuts.flushlist
    local getdata           = nuts.getdata

    local rulecodes         = nodes.rulecodes
    local normalrule_code   <const> = rulecodes.normal
    local boxrule_code      <const> = rulecodes.box
    local imagerule_code    <const> = rulecodes.image
    local emptyrule_code    <const> = rulecodes.empty
    local userrule_code     <const> = rulecodes.user
    local overrule_code     <const> = rulecodes.over
    local underrule_code    <const> = rulecodes.under
    local fractionrule_code <const> = rulecodes.fraction
    local radicalrule_code  <const> = rulecodes.radical
    local outlinerule_code  <const> = rulecodes.outline
    ----- virtualrule_code  <const> = rulecodes.virtual

    local vertical_rule     <const> = tex.ruleoptioncodes.vertical
    local horizontal_rule   <const> = tex.ruleoptioncodes.horizontal

    local processrule       = nodes.rules.process

    local f_fm = formatters["/Fm%d Do"]
    local f_im = formatters["/Im%d Do"]
    local f_gr = formatters["/Gp%d Do"]
    local f_st = formatters["/Gs%d gs"]

    local s_b <const> = "q"
    local s_e <const> = "Q"

    local f_v   = formatters["[] 0 d 0 J %.6N w 0 0 m %.6N 0 l S"]
    local f_h   = formatters["[] 0 d 0 J %.6N w 0 0 m 0 %.6N l S"]

    local f_f   = formatters["0 0 %.6N %.6N re f"]
    local f_o   = formatters["[] 0 d 0 J 0 0 %.6N %.6N re S"]
    local f_w   = formatters["[] 0 d 0 J %.6N w 0 0 %.6N %.6N re S"]

    local f_b   = formatters["%.6N w 0 %.6N %.6N %.6N re f"]
    local f_x   = formatters["[] 0 d 0 J %.6N w %.6N %.6N %.6N %.6N re S"]
    local f_y   = formatters["[] 0 d 0 J %.6N w %.6N %.6N %.6N %.6N re S %.6N 0 m %.6N 0 l S"]

 -- local f_v_d = formatters["[%.6N %.6N] 0 d 0 J %.6N w 0 0 m %.6N 0 l S"]
 -- local f_h_d = formatters["[%.6N %.6N] 0 d 0 J %.6N w 0 0 m 0 %.6N l S"]

    local f_d_h = formatters["[%.6N %.6N] 0 d 0 J %.6N w 0 %.6N m %.6N %.6N l S"]
    local f_d_v = formatters["[%.6N %.6N] 0 d 0 J %.6N w %.6N 0 m %.6N %.6N l S"]

    -- Historically the index is an object which is kind of bad.

    local boxresources, n = { }, 0

    getxformname = function(index)
        local l = boxresources[index]
        if l then
            return l.name
        else
            report("no box resource %S",index)
        end
    end

    lpdf.getxformname = getxformname

    local pdfcollectedresources = lpdf.collectedresources

    function codeinjections.saveboxresource(box,attributes,resources,immediate,kind,margin,onum)
        n = n + 1
        local immediate = true
        local margin    = margin or 0 -- or dimension
        local objnum    = onum or pdfreserveobject()
        local list      = tonut(type(box) == "number" and tex.takebox(box) or box)
        --
        if resources == true then
            resources = pdfcollectedresources {
                serialize = false,
            }
        end
        --
        local width, height, depth = getwhd(list)
-- print(width,height,depth)
        --
        local l = {
            width      = width,
            height     = height,
            depth      = depth,
            margin     = margin,
            attributes = attributes,
            resources  = resources,
            list       = nil,
            type       = kind,
            name       = n,
            index      = objnum,
            objnum     = objnum,
        }
        local r = boxresources[objnum]
        if r then
            flushlist(l.list)
            l.list = nil -- added
        end
        boxresources[objnum] = l
        if immediate then
            localconverter(list,"xform",objnum,l)
            flushedxforms[objnum] = { true , objnum }
            flushlist(list)
        else
            l.list = list
        end
        return objnum
    end

    function nodeinjections.useboxresource(index,wd,ht,dp)
        local l = boxresources[index]
        if l then
            if wd or ht or dp then
                wd, ht, dp = wd or 0, ht or 0, dp or 0
            else
                wd, ht, dp = l.width, l.height, l.depth
            end
            local rule = newboxrule(wd,ht,dp)
            setattrlist(rule,true)
            setprop(rule,"index",index)
            return tonode(rule), wd, ht, dp
        else
            report("no box resource %S",index)
        end
    end

    local function getboxresourcedimensions(index)
        local l = boxresources[index]
        if l then
            return l.width, l.height, l.depth, l.margin
        else
            report("no box resource %S",index)
        end
    end

    nodeinjections.getboxresourcedimensions = getboxresourcedimensions

    function codeinjections.getboxresourcebox(index)
        local l = boxresources[index]
        if l then
            return l.list
        end
    end

    -- a bit of a mess: index is now objnum but that has to change to a proper index
    -- ... an engine inheritance

    local function flushpdfxform(current,pos_h,pos_v,pos_r,size_h,size_v)
        -- object properties
        local objnum = getprop(current,"index")
        local name   = getxformname(objnum)
        local info   = flushedxforms[objnum]
        local r      = boxresources[objnum]
        if not info then
            info = { false , objnum }
            flushedxforms[objnum] = info
        end
        local wd, ht, dp = getboxresourcedimensions(objnum)
     -- or:   wd, ht, dp = r.width, r.height, r.depth
        -- sanity check
        local htdp = ht + dp
        if wd == 0 or size_h == 0 or htdp == 0 or size_v == 0 then
            return
        end
        -- calculate scale
        local rx, ry = 1, 1
        if wd ~= size_h or htdp ~= size_v then
            rx = size_h / wd
            ry = size_v / htdp
        end
        -- flush the reference
        usedxforms[objnum] = true
        pdf_goto_pagemode()
        calc_pdfpos(pos_h,pos_v)
        local tx = cmtx * bpfactor
        local ty = cmty * bpfactor
        b = b + 1 ; buffer[b] = s_b
     -- b = b + 1 ; buffer[b] = f_cm(rx,0,0,ry,tx,ty)
        b = b + 1 ; buffer[b] = f_cz(rx,    ry,tx,ty)
        b = b + 1 ; buffer[b] = f_fm(name)
        b = b + 1 ; buffer[b] = s_e
    end

    -- place image also used in vf but we can use a different one if we need it

    local imagetypes     = images.types -- pdf png jpg jp2 jbig2 stream
    local img_none       = imagetypes.none
    local img_pdf        = imagetypes.pdf
    local img_stream     = imagetypes.stream

    local one_bp <const> = 65536 * bpfactor

    local imageresources, n = { }, 0

    getximagename = function(index) -- not used
        local l = imageresources[index]
        if l then
            return l.name
        else
            report("no image resource %S",index)
        end
    end

    -- Groups are flushed immediately but we can decide to make them into a
    -- specific whatsit ... but not now. We could hash them if needed when
    -- we use lot sof them in mp ... but not now.

    usedxgroups = { }

    local flushgroup do

        local groups = 0
        local group  = nil
        local states = 0
        local names  = { }

        flushgroup = function(content,bbox,detail)
            if not group then
                group = pdfdictionary {
                    Type = pdfconstant("Group"),
                    S    = pdfconstant("Transparency"),
                }
            end
            local wrapper = pdfdictionary {
                Type      = pdf_xobject,
                Subtype   = pdf_form,
                FormType  = 1,
                Group     = group,
                BBox      = pdfarray(bbox),
                Resources = lpdf.collectedresources {
                    serialize = false
                }, -- what if empty
            }
            local objnum = pdfflushstreamobject(content,wrapper,false) -- why not compressed ?
            groups = groups + 1
            usedxgroups[groups] = objnum
            local kind = detail and detail.type == "luminosity"
            local result = f_gr(groups)
            if not detail then
                -- nothing special
            elseif detail.type == "luminosity" then
                local action = detail.action
                if action == "register" then
                    local state = pdfdictionary {
                        Type  = pdfconstant("ExtGState"),
                        AIS   = false,
                        CA    = 1,
                        ca    = 1,
                        SMask = pdfdictionary {
                            G    = pdfreference(objnum),
                            S    = pdfconstant("Luminosity"),
                            Type = pdfconstant("Mask"),
                        }
                    }
                    states = states + 1
                    lpdf.adddocumentextgstate("Gs" .. states,pdfreference(pdfflushobject(state)))
                    names[detail.name or "default"] = f_st(states)
                    return
                elseif action == "apply" then
                    local name  = detail.name or "default"
                    local delay = names[name]
                    if delay then
                        result = delay .. " " .. result
                        names[name] = nil
                    end
                else
                    -- error
                end
            end
            return result
        end

    end

    flushers.group  = flushgroup
    lpdf.flushgroup = flushgroup -- todo: access via driver in mlib-pps

    -- end of experiment

    local function flushpdfximage(current,pos_h,pos_v,pos_r,size_h,size_v)

        local width,
              height,
              depth     = getwhd(current)
        local total     = height + depth
        local transform = getprop(current,"transform") or 0  -- we never set it ... so just use rotation then
        local index     = getprop(current,"index") or 0
        local kind,
              xorigin,
              yorigin,
              xsize,
              ysize,
              rotation, -- transform / orientation / rotation : it's a mess (i need to redo this)
              objnum,
              groupref  = pdfincludeimage(index)  -- needs to be sorted out, bad name (no longer mixed anyway)

        if not kind then
            report("invalid image %S",index)
            return
        end

        local rx, sx, sy, ry, tx, ty = 1, 0, 0, 1, 0, 0

        -- tricky: xsize and ysize swapped

        if kind == img_pdf or kind == img_stream then
            rx, ry, tx, ty = 1/xsize, 1/ysize, xorigin/xsize, yorigin/ysize
        else
         -- if kind == img_png then
         --  -- if groupref > 0 and img_page_group_val == 0 then
         --  --     img_page_group_val = groupref
         --  -- end
         -- end
            rx, ry = bpfactor, bpfactor
        end

        if (transform & 7) > 3 then
            -- mirror
            rx, tx = -rx, -tx
        end
        local t = (transform + rotation) & 3
        if t == 0 then
            -- nothing
        elseif t == 1 then
            -- rotation over 90 degrees (counterclockwise)
            rx, sx, sy, ry, tx, ty = 0, rx, -ry, 0, -ty, tx
        elseif t == 2 then
            -- rotation over 180 degrees (counterclockwise)
            rx, ry, tx, ty = -rx, -ry, -tx, -ty
        elseif t == 3 then
            -- rotation over 270 degrees (counterclockwise)
            rx, sx, sy, ry, tx, ty = 0, -rx, ry, 0, ty, -tx
        end

        rx = rx * width
        sx = sx * total
        sy = sy * width
        ry = ry * total
        tx = pos_h - tx * width
        ty = pos_v - ty * total

        local t = transform + rotation

        if (transform & 7) > 3 then
            t = t + 1
        end

        t = t & 3

        if t == 0 then
            -- no transform
        elseif t == 1 then
            -- rotation over 90 degrees (counterclockwise)
            tx = tx + width
        elseif t == 2 then
            -- rotation over 180 degrees (counterclockwise)
            tx = tx + width
            ty = ty + total
        elseif t == 3 then
            -- rotation over 270 degrees (counterclockwise)
            ty = ty + total
        end

     -- a flaw in original, can go:
     --
     -- if img_page_group_val == 0 then
     --     img_page_group_val = group_ref
     -- end

        usedximages[index] = objnum -- hm

        pdf_goto_pagemode()

        calc_pdfpos(tx,ty)

        tx = cmtx * bpfactor
        ty = cmty * bpfactor

        b = b + 1 ; buffer[b] = s_b
        b = b + 1 ; buffer[b] = f_cm(rx,sx,sy,ry,tx,ty)
        b = b + 1 ; buffer[b] = f_im(index)
        b = b + 1 ; buffer[b] = s_e
    end

    flushers.flushimage = function(index,width,height,depth,pos_h,pos_v)

        -- used in vf characters

        local total = height + depth
        local kind,
              xorigin, yorigin,
              xsize, ysize,
              rotation,
              objnum,
              groupref = pdfincludeimage(index)

        local rx = width / xsize
        local sx = 0
        local sy = 0
        local ry = total / ysize
        local tx = pos_h
        -- to be sorted out
     -- local ty = pos_v - depth
        local ty = pos_v -- we assume that depth is dealt with in the caller (for now)
        usedximages[index] = objnum

        pdf_goto_pagemode()

        calc_pdfpos(tx,ty)

        tx = cmtx * bpfactor
        ty = cmty * bpfactor

        b = b + 1 ; buffer[b] = s_b
        b = b + 1 ; buffer[b] = f_cm(rx,sx,sy,ry,tx,ty)
        b = b + 1 ; buffer[b] = f_im(index)
        b = b + 1 ; buffer[b] = s_e
    end

    -- For the moment we need this hack because the engine checks the 'image'
    -- command in virtual fonts (so we use lua instead).
    --
    -- These will be replaced by a new more advanced one ... some day ... or
    -- never because the next are like the other engines and compensate for
    -- small sizes which is needed for inaccurate viewers.

    local function onoff(on,off,dim)
        on  = on  * bpfactor
        off = off * bpfactor
        if on + off > dim then
            return dim, 0
        else
            local n = ((dim -on) // (on + off)) + 1
            if n > 1 then
                off = (dim - n * on)/(n - 1)
            end
            return on, off
        end
    end

    flushers.rule = function(current,pos_h,pos_v,pos_r,size_h,size_v,subtype,on,off,running)
        if subtype == emptyrule_code then
            return
        elseif subtype == boxrule_code then
            return flushpdfxform(current,pos_h,pos_v,pos_r,size_h,size_v)
        elseif subtype == imagerule_code then
            return flushpdfximage(current,pos_h,pos_v,pos_r,size_h,size_v)
        elseif subtype == userrule_code or (subtype >= overrule_code and subtype <= radicalrule_code) then
            pdf_goto_pagemode()
            b = b + 1 ; buffer[b] = s_b
            pdf_set_pos_temp(pos_h,pos_v)
            processrule(current,size_h,size_v,pos_r) -- so we pass direction
            b = b + 1 ; buffer[b] = s_e
            return
        end

        pdf_goto_pagemode()

        b = b + 1 ; buffer[b] = s_b

        local dim_h = size_h * bpfactor
        local dim_v = size_v * bpfactor
        local rule
        --
        -- this fails for showglyphs so and i have no reason to look into it now and rectangles
        -- do a better job anyway
        --
        if subtype == outlinerule_code then
            local linewidth = getdata(current)
            pdf_set_pos_temp(pos_h,pos_v)
            if linewidth > 0 then
                rule = f_w(linewidth * bpfactor,dim_h,dim_v)
            else
                rule = f_o(dim_h,dim_v)
            end
        elseif on ~= 0 and off ~= 0 then -- and subtype ~= strutrule_code
            local options    = getoptions(current)
            local horizontal = (options & horizontal_rule) ~= 0
            local vertical   = (options & vertical_rule  ) ~= 0
            if not horizontal and not vertical then
                horizontal = true -- and no check for running, see autorule in tabulate
            elseif not running then
                horizontal = not horizontal
            end
            if horizontal then
                local offset = dim_v/2
                on, off = onoff(on,off,dim_h)
                pdf_set_pos_temp(pos_h,pos_v)
                rule = f_d_h(on,off,dim_v,offset,dim_h,offset)
            else
                local offset = dim_h/2
                on, off = onoff(on,off,dim_v)
                pdf_set_pos_temp(pos_h,pos_v)
                rule = f_d_v(on,off,dim_h,offset,offset,dim_v)
            end
        elseif dim_v <= one_bp then
            pdf_set_pos_temp(pos_h,pos_v + 0.5 * size_v)
            rule = f_v(dim_v,dim_h)
        elseif dim_h <= one_bp then
            pdf_set_pos_temp(pos_h + 0.5 * size_h,pos_v)
            rule = f_h(dim_h,dim_v)
        else
            pdf_set_pos_temp(pos_h,pos_v)
            rule = f_f(dim_h,dim_v)
        end

        b = b + 1 ; buffer[b] = rule
        b = b + 1 ; buffer[b] = s_e

    end

    flushers.simplerule = function(pos_h,pos_v,pos_r,size_h,size_v)
        pdf_goto_pagemode()

        b = b + 1 ; buffer[b] = s_b

        local dim_h = size_h * bpfactor
        local dim_v = size_v * bpfactor
        local rule

        if dim_v <= one_bp then
            pdf_set_pos_temp(pos_h,pos_v + 0.5 * size_v)
            rule = f_v(dim_v,dim_h)
        elseif dim_h <= one_bp then
            pdf_set_pos_temp(pos_h + 0.5 * size_h,pos_v)
            rule = f_h(dim_h,dim_v)
        else
            pdf_set_pos_temp(pos_h,pos_v)
            rule = f_f(dim_h,dim_v)
        end

        b = b + 1 ; buffer[b] = rule
        b = b + 1 ; buffer[b] = s_e
    end

    flushers.specialrule = function(pos_h,pos_v,pos_r,width,height,depth,line,outline,baseline)
        pdf_goto_pagemode()

        b = b + 1 ; buffer[b] = s_b

        local width  = bpfactor * width
        local height = bpfactor * height
        local depth  = bpfactor * depth
        local total  = height + depth
        local line   = bpfactor * line
        local half   = line / 2
        local rule

        if outline then
            local d = -depth + half
            local w =  width - line
            local t =  total - line
            if baseline and w > 0 then
                rule = f_y(line,half,d,w,t,half,w)
            else
                rule = f_x(line,half,d,w,t)
            end
        else
            rule = f_b(line,-depth,width,total)
        end
        pdf_set_pos_temp(pos_h,pos_v)

        b = b + 1 ; buffer[b] = rule
        b = b + 1 ; buffer[b] = s_e
    end

end

--- basics

local wrapupdocument, registerpage  do

    local pages    = { }
    local maxkids  = 10
    local nofpages = 0
    local pagetag  = "unset"

    registerpage = function(object)
        nofpages = nofpages + 1
        local objnum = pdfpagereference(nofpages)
        pages[nofpages] = {
            page   = nofpages, -- original number, only for diagnostics
            objnum = objnum,
            object = object,
            tag    = pagetag,
        }
    end

    function lpdf.setpagetag(tag)
        pagetag = tag or "unset"
    end

    function lpdf.getnofpages()
        return nofpages
    end

    function lpdf.getpagetags()
        local list = { }
        for i=1,nofpages do
            list[i] = pages[i].tag
        end
        return list
    end

    function lpdf.setpageorder(mapping,p)
        -- mapping can be a hash so:
        local list = table.sortedkeys(mapping)
        local n    = #list
        local nop  = p or nofpages
        if n == nop then
            local done = { }
            local hash = { }
            for i=1,n do
                local order = mapping[list[i]]
                if hash[order] then
                    report("invalid page order, duplicate entry %i",order)
                    return
                elseif order < 1 or order > nofpages then
                    report("invalid page order, no page %i",order)
                    return
                else
                    done[i]     = pages[order]
                    hash[order] = true
                end
            end
            pages = done
        else
            report("invalid page order, %i entries expected",nop)
        end
    end

    -- We can have this, but then via codeinjections etc. Later.

 -- function structures.pages.swapthem()
 --     local n = lpdf.getnofpages()
 --     local t = { }
 --     for i=1,n do
 --         t[i] = i
 --     end
 --     for i=2,math.odd(n) and n or (n-1),2 do
 --         t[i]   = i+1
 --         t[i+1] = i
 --     end
 --     lpdf.setpageorder(t)
 -- end

    wrapupdocument = function(driver)

        -- hook (to reshuffle pages)
        local pagetree = { }
        local parent   = nil
        local minimum  = 0
        local maximum  = 0
        local current  = 0
        if #pages > 1.5 * maxkids then
            repeat
                local plist, pnode
                if current == 0 then
                    plist, minimum = pages, 1
                elseif current == 1 then
                    plist, minimum = pagetree, 1
                else
                    plist, minimum = pagetree, maximum + 1
                end
                maximum = #plist
                if maximum > minimum then
                    local kids
                    for i=minimum,maximum do
                        local p = plist[i]
                        if not pnode or #kids == maxkids then
                            kids   = pdfarray()
                            parent = pdfreserveobject()
                            pnode  = pdfdictionary {
                                objnum = parent,
                                Type   = pdf_pages,
                                Kids   = kids,
                                Count  = 0,
                            }
                            pagetree[#pagetree+1] = pnode
                        end
                        kids[#kids+1] = pdfreference(p.objnum)
                        pnode.Count = pnode.Count + (p.Count or 1)
                        p.Parent = pdfreference(parent)
                    end
                end
                current = current + 1
            until maximum == minimum
            -- flush page tree
            for i=1,#pagetree do
                local entry  = pagetree[i]
                local objnum = entry.objnum
                entry.objnum = nil
                pdfflushobject(objnum,entry)
            end
        else
            -- ugly
            local kids = pdfarray()
            local list = pdfdictionary {
                Type  = pdf_pages,
                Kids  = kids,
                Count = nofpages,
            }
            parent = pdfreserveobject()
            for i=1,nofpages do
                local page = pages[i]
                kids[#kids+1] = pdfreference(page.objnum)
                page.Parent = pdfreference(parent)
            end
            pdfflushobject(parent,list)
        end
        for i=1,nofpages do
            local page   = pages[i]
            local object = page.object
            object.Parent = page.Parent
            pdfflushobject(page.objnum,object)
        end
        lpdf.addtocatalog("Pages",pdfreference(parent))
    end

end

local function initialize(driver,details)
    reset_variables(details)
    reset_buffer()
end

-- This will all move and be merged and become less messy.

-- todo: more clever resource management: a bit tricky as we can inject
-- stuff in the page stream

local compact       = false
local encryptstream = false
local encryptobject = false
local encdict       = nil
local majorversion  = 1
local minorversion  = 7

-- Encryption

-- This stuff is poorly documented so it took a while to figure out a way that made
-- loading in a few programe working. Of course one you see the solution one can
-- claim that it's easy and trivial. In the end we could even make acrobat accepting
-- the file: it doesn't like the catalog to be in an object stream which to me
-- smells like a bug.

do

    -- move up (some already) or better: lpdf-aes.lmt or so

    local byte, sub, bytes, tohex, tobytes = string.byte, string.sub, string.bytes, string.tohex, string.tobytes
    local P, S, V, Cs, lpegmatch, patterns = lpeg.P, lpeg.S, lpeg.V, lpeg.Cs, lpeg.match, lpeg.patterns

    local digest256 = sha2.digest256
    local digest384 = sha2.digest384
    local digest512 = sha2.digest512

    local aesencode = aes.encode
    local aesdecode = aes.decode
    local aesrandom = aes.random

    -- random and padding functions are gone here

    local function validpassword(str)
        return #str > 127 and sub(str,1,127) or str
    end

    local encryptionkey = false
    local objectparser  = false

    do

        local function ps_encrypt(str)
            -- string is already unescaped
            str = aesencode(str,encryptionkey,true,true,true)
            return "<" .. tohex(str) .. ">"
        end

        local function hex_encrypt(str)
            -- string needs to be decoded
            str = tobytes(str)
            str = aesencode(str,encryptionkey,true,true,true)
            return "<" .. tohex(str) .. ">"
        end

        local whitespace  = S("\000\009\010\012\013\032")^1
        local anything    = patterns.anything
        local space       = patterns.space
        local spacing     = whitespace^0
        local newline     = patterns.eol
        local cardinal    = patterns.cardinal

        local p_psstring  = (
                              P("(")
                            * Cs(P { ( P("\\")/"" * anything + P("(") * V(1) * P(")") + (1 - P(")")) )^0 })
                            * P(")")
                          ) / ps_encrypt

        local p_hexstring = (
                              P("<")
                            * Cs((1-P(">"))^1)
                            * P(">")
                          ) / hex_encrypt

        local p_comment   = P("%") * (1-newline)^1 * newline^1
        local p_name      = P("/") * (1 - whitespace - S("<>/[]()"))^1
        local p_number    = patterns.number
        local p_boolean   = P("true") + P("false")
        local p_null      = P("null")
        local p_reference = cardinal * spacing * cardinal * spacing * P("R")

        local p_other     = p_name + p_reference + p_psstring + p_hexstring + p_number
                          + p_boolean + p_null + p_comment

        local p_dictionary  = { "dictionary",
            dictionary = (
                P("<<")
              * (spacing * p_name * spacing * V("whatever"))^0
              * spacing
              * P(">>")
            ),
            array = (
                P("[")
              * (spacing * V("whatever"))^0
              * spacing
              * P("]")
            ),
            whatever = (
                V("dictionary")
              + V("array")
              + p_other
            ),
        }

        local p_object = P { "object",
            dictionary = p_dictionary.dictionary,
            array      = p_dictionary.array,
            whatever   = p_dictionary.whatever,
            object     = spacing * (V("dictionary") + V("array") + p_other)
        }

     -- local p_object = cardinal
     --                * spacing
     --                * cardinal
     --                * spacing
     --                * P("obj")
     --                * p_object
     --                * P(1)^0
     --
     -- objectparser = Cs(p_object^1)

        objectparser = Cs(p_object^1)

    end

    local function makehash(password,salt,userkey)
        local k = digest256(password .. salt .. (userkey or ""))
        local n = 0
        while true do
            local k1 = rep(password .. k .. (userkey or ""),64)
            local k2 = sub(k,1,16)
            local iv = sub(k,17,32)
            local e = aesencode(k1,k2,iv)
            local m = 0
            local i = 1
            for b in bytes(e) do
                m = m + b
                if i == 16 then
                    break
                else
                    i = i + 1
                end
            end
            m = m % 3
            if m == 0 then
                k = digest256(e)
            elseif m == 1 then
                k = digest384(e)
            else
                k = digest512(e)
            end
            n = n + 1
            if n >= 64 and byte(sub(e,-1)) <= (n - 32) then
                break
           end
        end
        return sub(k,1,32)
    end

    local options = lpdf.permissions -- or { }

    -- 1111 0000 1100 0011

    local mandate  = 0x0200
    local defaults = options.print | options.extract | options.quality

    -- majorversion = 2
    -- minorversion = 0

    function lpdf.setencryption(specification)
        if not encryptstream then
            local ownerpassword = specification.ownerpassword
            local userpassword  = specification.userpassword
            local optionlist    = specification.permissions
            if type(ownerpassword) == "string" and ownerpassword ~= "" then
                --
                if type(userpassword) ~= "string" then
                    userpassword = ""
                end
                userpassword  = validpassword(userpassword)
                ownerpassword = validpassword(ownerpassword)
                --
                encryptionkey = aesrandom(32) -- used earlier on
                --
                local permissions = mandate
                if optionlist then
                    optionlist = utilities.parsers.settings_to_array(optionlist)
                    for i=1,#optionlist do
                        local p = options[optionlist[i]]
                        if p then
                            permissions = permissions | p
                        end
                    end
                else
                    permissions = permissions | defaults
                end
                --
                permissions = permissions | 0xF0C3 -- needs work
                --
                optionlist = { }
                for k, v in sortedhash(options) do
                    if permissions & v == v then
                        optionlist[#optionlist+1] = k
                    end
                end
                --
                local uservalidationsalt  = aesrandom(8)
                local userkeysalt         = aesrandom(8)
                local userhash            = makehash(userpassword,uservalidationsalt)
                local userkey             = userhash .. uservalidationsalt .. userkeysalt -- U
                local userintermediate    = makehash(userpassword,userkeysalt)
                local useraes             = aesencode(encryptionkey,userintermediate) -- UE
                --
                local ownervalidationsalt = aesrandom(8)
                local ownerkeysalt        = aesrandom(8)
                local ownerhash           = makehash(ownerpassword,ownervalidationsalt,userkey)
                local ownerkey            = ownerhash .. ownervalidationsalt .. ownerkeysalt -- O
                local ownerintermediate   = makehash(ownerpassword,ownerkeysalt,userkey)
                local owneraes            = aesencode(encryptionkey,ownerintermediate) -- OE
                --
                -- still not ok test in qpdf
                --
                local permissionsstring   = sio.tocardinal4(0xFFFFFFFF)
                                         .. sio.tocardinal4(permissions)
                                         .. "T" -- EncryptMetadata
                                         .. "adb"
                                         .. aesrandom(4)
                local permissionsaes      = aesencode(permissionsstring,encryptionkey)
                --
                permissionsaes = tohex(permissionsaes)
                userkey        = tohex(userkey)
                ownerkey       = tohex(ownerkey)
                useraes        = tohex(useraes)
                owneraes       = tohex(owneraes)
                --
                encdict  = pdfdictionary {
                    Filter = pdfconstant("Standard"),
                    V      = 5,   -- variant
                    R      = 6,   -- revision
                    Length = 256, -- not needed
                    StmF   = pdfconstant("StdCF"),
                    StrF   = pdfconstant("StdCF"),
                    P      = permissions,
                    Perms  = pdfliteral(permissionsaes,true), -- #16
                    U      = pdfliteral(userkey,       true), -- #48
                    O      = pdfliteral(ownerkey,      true), -- #48
                    UE     = pdfliteral(useraes,       true), -- #32
                    OE     = pdfliteral(owneraes,      true), -- #32
                    CF     = {
                        StdCF = {
                            AuthEvent = pdfconstant("DocOpen"),
                            CFM       = pdfconstant("AESV3"),
                            Length    = 32, -- #encryptionkey
                        }
                    },
                    -- bonus
                    EncryptMetadata = true,
                }
                --
                encryptstream = function(str)
                    return aesencode(str,encryptionkey,true,true,true) -- random-iv add-iv add-padding
                end
                encryptobject = function(obj)
                    if obj then
                        if type(obj) == "table" then
                            -- We could just loop over the table and encrypt strings
                            -- before we serialize but we gain little and we can get
                            -- already serialized objects anyway (mostly anyway).
                            obj = obj()
                        end
                        return lpegmatch(objectparser,obj) or obj
                    end
                end
                --
                report_encryption("stream objects get encrypted")
                if not objectstream then
                    report_encryption("strings are not encrypted, enable object streams")
                end
                report_encryption("permissions: % t",optionlist)
                if userpassword == "" then
                    report_encryption("no user password")
                end
                --
            end
        end
    end

    backends.registered.pdf.codeinjections.setencryption = lpdf.setencryption

end

do

    -- This is more a convenience feature and it might even be not entirely robust.
    -- It removes redundant color directives which makes the page stream look a bit
    -- nicer (also when figuring out issues). I might add more here but there is
    -- some additional overhead involved so runtime can be impacted.

    local P, R, S, Cs, lpegmatch = lpeg.P, lpeg.R, lpeg.S, lpeg.Cs, lpeg.match

    local p_ds    = (R("09") + S(" ."))^1
    ----- p_nl    = S("\n\r")^1
    local p_nl    = S("\n")^1
    local p_eg    = P("Q")

    local p_cl    = p_ds * (P("rg") + P("g") + P("k")) * p_ds * (P("RG") + P("G") + P("K"))
    ----- p_cl    = (p_ds * (P("rg") + P("g") + P("k") + P("RG") + P("G") + P("K")))^1
    local p_tr    = P("/Tr") * p_ds * P("gs")

    local p_no_cl = (p_cl * p_nl) / ""
    local p_no_tr = (p_tr * p_nl) / ""
    local p_no_nl = 1 - p_nl

    local p_do_cl = p_cl * p_nl
    local p_do_tr = p_tr * p_nl

    local p_do_eg = p_eg * p_nl

    local pattern = Cs( (
        (p_no_cl + p_no_tr)^0 * p_do_eg           -- transparencies and colors before Q
      +  p_no_tr * p_no_cl    * p_do_tr * p_do_cl -- transparencies and colors before others
      +  p_no_cl              * p_do_cl           -- successive colors
      +  p_no_tr              * p_do_tr           -- successive transparencies
      +  p_no_nl^1
      +  1
    )^1 )

    local oldsize = 0
    local newsize = 0

    directives.register("backend.pdf.compact", function(v)
        compact = v and function(s)
            oldsize = oldsize + #s
            s = lpegmatch(pattern,s) or s
            newsize = newsize + #s
            return s
        end
    end)

    statistics.register("pdf pagestream",function()
        if oldsize ~= newsize then
            return string.format("old size: %i, new size %i",oldsize,newsize)
        end
    end)

end

local flushdeferred -- defined later

local level = 0
local state = true

function lpdf.setpagestate(s)
    state = s
end

local finalize  do

    local ceil  = math.ceil
    local floor = math.floor

    local f_font  = formatters["F%d"]

    local f_form  = formatters["Fm%d"]
    local f_group = formatters["Gp%d"]
    local f_image = formatters["Im%d"]
    local f_state = formatters["Gs%d"]

    local function checkedbox(mediabox,otherbox,what)
        if otherbox and #mediabox == 4 and #otherbox == 4 then
            local done = false
            if otherbox[1] < mediabox[1] then done = true ; otherbox[1] = mediabox[1] end
            if otherbox[2] < mediabox[2] then done = true ; otherbox[2] = mediabox[2] end
            if otherbox[3] > mediabox[3] then done = true ; otherbox[3] = mediabox[3] end
            if otherbox[4] > mediabox[4] then done = true ; otherbox[4] = mediabox[4] end
            if done then
                report("limiting %a to 'MediaBox'",what)
            end
        end
        return otherbox
    end

    -- The Resources dictionary always was optional but for some reason (maybe because some
    -- application bark on it not being present, which makes bugs into a feature) it now is
    -- (per addendum to the standard). First it was only for version 2, but in the end also
    -- version 1 has been affected. Of course that makes older documents that just omit an
    -- empty one officially invalid. However, because often the procset was there anyway,
    -- changes are low, but as that one is also obsolete, one only gets an issue when that
    -- one is left out too. In the end it means that we always need to add a bogus resource
    -- in order to satisfy 'strict' checkers (one can argue that deprecated features should
    -- be reported as such, and not as error).

    local dummy = false

    local function checkedresources()
        if dummy == false then
         -- if majorversion > 1 then
                dummy = pdfsharedobject(pdfdictionary {
                 -- LMTX_Comment = pdfunicode("mandate resource dictionary cf 2.0+ specification")
                    LMTX_Comment = pdfunicode("mandate resource dictionary cf 'latest' specification")
                } )
         -- else
         --     dummy = nil
         -- end
        end
        return dummy
    end

    lpdf.checkedresources = checkedresources

    finalize = function(driver,details)

        if not details then
            report("something is wrong, no details in 'finalize'")
        end

        level = level + 1

        pdf_goto_pagemode() -- for now

        local objnum        = details.objnum
        local specification = details.specification or { }

        local content = concat(buffer,"\n",1,b)

        if compact then
            content = compact(content)
        end

        local fonts   = nil
        local xforms  = nil

        if next(usedfonts) then
            fonts = pdfdictionary { }
            for k, v in next, usedfonts do
                fonts[f_font(v)] = pdfreference(usedfontobjects[k]) -- we can overload for testing
            end
        end

        -- messy: use real indexes for both ... so we need to change some in the
        -- full luatex part

        if next(usedxforms) or next(usedximages) or next(usedxgroups) then
            xforms = pdfdictionary { }
            for k in sortedhash(usedxforms) do
                xforms[f_form(getxformname(k))] = pdfreference(k)
            end
            for k, v in sortedhash(usedximages) do
                xforms[f_image(k)] = pdfreference(v)
            end
            for k, v in sortedhash(usedxgroups) do
                xforms[f_group(k)] = pdfreference(v)
            end
        end

        reset_buffer()

     -- finish_pdfpage_callback(shippingmode == "page")

        if shippingmode == "page" then

            local pageproperties  = lpdf.getpageproperties()

            local pageresources   = pageproperties.pageresources
            local pageattributes  = pageproperties.pageattributes
            local pagesattributes = pageproperties.pagesattributes

            pageresources.Font    = fonts
            pageresources.XObject = xforms
         -- pageresources.ProcSet = lpdf.procset()

            local bbox = pdfarray {
                boundingbox[1] * bpfactor,
                boundingbox[2] * bpfactor,
                boundingbox[3] * bpfactor,
                boundingbox[4] * bpfactor,
            }

            local contentsobj = content ~= "" and pdfflushstreamobject(content,false,true) or false -- empty /Contents are forbidden

            pageattributes.Type      = pdf_page
            pageattributes.Contents  = contentsobj and pdfreference(contentsobj) or nil
            pageattributes.MediaBox  = pdfsharedobject(bbox)
            pageattributes.Parent    = nil -- precalculate
            pageattributes.Group     = nil -- todo

            -- resources can be indirect

            if state == "ignore" or state == false then

            else

                registerpage(pageattributes)

                lpdf.finalizepage(true)

                local TrimBox  = pageattributes.TrimBox
                local CropBox  = pageattributes.CropBox
                local BleedBox = pageattributes.BleedBox

                -- Indirect objects don't work in all viewers.

                if TrimBox  then pageattributes.TrimBox  = pdfsharedobject(checkedbox(bbox,TrimBox,"TrimBox")) end
                if CropBox  then pageattributes.CropBox  = pdfsharedobject(checkedbox(bbox,CropBox,"CropBox")) end
                if BleedBox then pageattributes.BleedBox = pdfsharedobject(checkedbox(bbox,BleedBox,"BleedBox")) end

            end

            pageattributes.Resources = next(pageresources) and pdfsharedobject(pageresources) or checkedresources()

        else

            local xformtype  = specification.type or 0
            local margin     = specification.margin or 0
            local attributes = specification.attributes or ""
            local resources  = specification.resources or ""
            local wrapper    = nil

            if xformtype == 0 then
                wrapper = pdfdictionary {
                    Type      = pdf_xobject,
                    Subtype   = pdf_form,
                    FormType  = 1,
                    BBox      = nil,
                    Matrix    = nil,
                    Resources = nil,
                }
            else
                wrapper = pdfdictionary {
                    BBox      = nil,
                    Matrix    = nil,
                    Resources = nil,
                }
            end
            if xformtype == 0 or xformtype == 1 or xformtype == 3 then
                margin = 1
                wrapper.BBox = pdfarray {
                     floor(boundingbox[1] * bpfactor) - margin,
                     floor(boundingbox[2] * bpfactor) - margin,
                     ceil (boundingbox[3] * bpfactor) + margin,
                     ceil (boundingbox[4] * bpfactor) + margin,
                }
            end
            if xformtype == 0 or xformtype == 2 or xformtype == 3 then
                -- can be shared too
                wrapper.Matrix = pdfarray { 1, 0, 0, 1, 0, 0 }
            end

            local patterns = true

            if attributes.Type and attributes.Type == pdf_pattern then
                patterns = false
            end

            local boxresources = lpdf.collectedresources {
                patterns  = patterns,
                serialize = false,
            }
            boxresources.Font    = fonts
            boxresources.XObject = xforms

         -- todo: maybe share them
         -- wrapper.Resources = pdfreference(pdfflushobject(boxresources))

            if resources ~= "" then
                boxresources = boxresources + resources
            end
            if attributes ~= "" then
                wrapper = wrapper + attributes
            end

        --  wrapper.Resources = next(boxresources) and boxresources or nil
            wrapper.Resources = next(boxresources) and boxresources or checkedresources()
        --  wrapper.ProcSet   = lpdf.procset()

            pdfflushstreamobject(content,wrapper,true,specification.objnum)

        end

        for objnum in sortedhash(usedxforms) do
            local f = flushedxforms[objnum]
            if f[1] == false then
                f[1] = true
                local objnum        = f[2] -- specification.objnum
                local specification = boxresources[objnum]
                local list          = specification.list
                localconverter(list,"xform",f[2],specification)
            end
        end

        pdf_h, pdf_v  = 0, 0

        if level == 1 then
            flushdeferred()
        end
        level = level - 1

    end

end

-- now comes the pdf file handling

local objects       = { }
local streams       = { } -- maybe just parallel to objects (no holes)
local nofobjects    = 0
local offset        = 0
local f             = false
local flush         = false
local objectstream  = true
local compress      = true
local cache         = false
local info          = ""
local catalog       = ""
local lastdeferred  = false

local f_object       = formatters["%i 0 obj\010%s\010endobj\010"]
local f_stream_n_u   = formatters["%i 0 obj\010<< /Length %i >>\010stream\010%s\010endstream\010endobj\010"]
local f_stream_n_c   = formatters["%i 0 obj\010<< /Filter /FlateDecode /Length %i >>\010stream\010%s\010endstream\010endobj\010"]
local f_stream_d_u   = formatters["%i 0 obj\010<< %s /Length %i >>\010stream\010%s\010endstream\010endobj\010"]
local f_stream_d_c   = formatters["%i 0 obj\010<< %s /Filter /FlateDecode /Length %i >>\010stream\010%s\010endstream\010endobj\010"]
local f_stream_d_r   = formatters["%i 0 obj\010<< %s >>\010stream\010%s\010endstream\010endobj\010"]

----- f_object_b     = formatters["%i 0 obj\010"]
local f_stream_b_n_u = formatters["%i 0 obj\010<< /Length %i >>\010stream\010"]
local f_stream_b_n_c = formatters["%i 0 obj\010<< /Filter /FlateDecode /Length %i >>\010stream\010"]
local f_stream_b_d_u = formatters["%i 0 obj\010<< %s /Length %i >>\010stream\010"]
local f_stream_b_d_c = formatters["%i 0 obj\010<< %s /Filter /FlateDecode /Length %i >>\010stream\010"]
local f_stream_b_d_r = formatters["%i 0 obj\010<< %s >>\010stream\010"]

----- s_object_e <const> = "\010endobj\010"
local s_stream_e <const> = "\010endstream\010endobj\010"

do

    -- Versions can be set but normally are managed by the official standards. When possible
    -- reading and writing should look at these values.

    function lpdf.setversion(major,minor)
        majorversion = tonumber(major) or majorversion
        minorversion = tonumber(minor) or minorversion
    end

    function lpdf.getversion(major,minor)
        return majorversion, minorversion
    end

    function lpdf.majorversion() return majorversion end
    function lpdf.minorversion() return minorversion end

    -- It makes no sense to support levels so we only enable and disable and stick to level 3
    -- which is both fast and efficient.

    local frozen = false
    local clevel = 3
    local olevel = 1

    function lpdf.setcompression(level,objectlevel,freeze)
        if not frozen then
            compress = level and level ~= 0 and true or false
         -- if next(objects) or next(streams) or nofobjects > 0 then -- too strict as we have one alreay
            if next(streams) then
                -- no way
            else
                objectstream = objectlevel and objectlevel ~= 0 and true or false
            end
            frozen = freeze
        end
    end

    function lpdf.getcompression()
        return compress and olevel or 0, objectstream and clevel or 0
    end

    function lpdf.compresslevel()
        return compress and olevel or 0
    end

    function lpdf.objectcompresslevel()
        return objectstream and clevel or 0
    end

    if environment.arguments.nocompression then
        lpdf.setcompression(0,0,true)
    end

    function lpdf.compressdata(data) -- how about encryption
        if data and compress then
            local size = #data
            local comp = compressdata(data,size)
            if comp and #comp < size then
                return comp
            else
                return false
            end
        else
            return false
        end
    end

end

local addtocache, flushcache, cache do

    local data, d  = { }, 0
    local list, l  = { }, 0
    local coffset  = 0
    local indices  = { }

    local maxsize  = 32 * 1024 -- uncompressed
    local maxcount = 0xFF

    addtocache = function(n,str)
        local size = #str
        if size == 0 then
            -- todo: message
            return
        end
        if coffset + size > maxsize or d == maxcount then
            flushcache()
        end
        if d == 0 then
            nofobjects = nofobjects + 1
            objects[nofobjects] = false
            streams[nofobjects] = indices
            cache = nofobjects
        end
        objects[n] = - cache
        indices[n] = d
        d = d + 1
        -- can have a comment n 0 obj as in luatex
        data[d] = str
        l = l + 1 ; list[l] = n
        l = l + 1 ; list[l] = coffset
        coffset = coffset + size + 1
    end

    local p_ObjStm = pdfconstant("ObjStm")

    flushcache = function() -- references cannot be stored
        if l > 0 then
            list = concat(list," ")
            data[0] = list
            data = concat(data,"\010",0,d)
            local strobj = pdfdictionary {
                Type  = p_ObjStm,
                N     = d,
                First = #list + 1,
            }
            objects[cache] = offset
            local fb
            if compress then
                local size = #data
                local comp = compressdata(data,size)
                if comp and #comp < size then
                    data = comp
                    fb = f_stream_b_d_c
                else
                    fb = f_stream_b_d_u
                end
            else
                fb = f_stream_b_d_u
            end
            local size = #data
            if encryptstream then
                data = encryptstream(data)
                size = #data
            end
            local b = fb(cache,strobj(),size)
            local e = s_stream_e
            flush(f,b)
            flush(f,data)
            flush(f,e)
            offset = offset + #b + size + #e
            data, d = { }, 0
            list, l = { }, 0
            coffset = 0
            indices = { }
        end
    end

end

do

    local names        = { }
    local cache        = { }
    local nofpages     = 0

    local texgetcount  = tex.getcount

    local c_realpageno <const> = tex.iscount("realpageno")

    pdfreserveobject = function(name)
        nofobjects = nofobjects + 1
        objects[nofobjects] = false
        if name then
            names[name] = nofobjects
            if trace_objects then
                report_objects("reserving number %a under name %a",nofobjects,name)
            end
        elseif trace_objects then
            report_objects("reserving number %a",nofobjects)
        end
        return nofobjects
    end

    pdfpagereference = function(n,complete) -- true | false | nil | n [true,false]
        if n == true or not n then
            complete = n
            n = texgetcount(c_realpageno)
        end
        if n > nofpages then
            nofpages = n
        end
        local r = pdfgetpagereference(n)
        return complete and pdfreference(r) or r
    end

    lpdf.reserveobject = pdfreserveobject
    lpdf.pagereference = pdfpagereference

    function lpdf.lastreferredpage()
        return nofpages
    end

    function lpdf.nofpages() -- this will change: document nofpages
        return structures.pages.nofpages
    end

    function lpdf.object(...)
        pdfdeferredobject(...)
    end

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

    pdfflushobject = function(name,data)
        if data then
            local named = names[name]
            if named then
                if not trace_objects then
                elseif trace_details 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_details 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_details then
                report_objects("flushing data: %S",name)
            end
            return pdfimmediateobject(tostring(name))
        end
    end

    pdfflushstreamobject = function(data,dict,compressed,objnum) -- default compressed
        if trace_objects then
            report_objects("flushing stream object of %s bytes",#data)
        end
        local dtype    = type(dict)
        local kind     = compressed == "raw" and "raw" or "stream"
        local nolength = nil
        if compressed == "raw" then
            compressed = false
            nolength   = true
         -- data       = string.formatters["<< %s >>stream\n%s\nendstream"](attr,data)
        end

        return pdfdeferredobject {
            objnum        = objnum,
            immediate     = true,
            nolength      = nolength,
            compresslevel = compressed,
            type          = "stream",
            string        = data,
            attr          = (dtype == "string" and dict) or (dtype == "table" and dict()) or nil,
        }
    end

    function lpdf.flushstreamfileobject(filename,dict,compressed,objnum) -- default compressed
        if trace_objects then
            report_objects("flushing stream file object %a",filename)
        end
        local dtype = type(dict)
        return pdfdeferredobject {
            objnum        = objnum,
            immediate     = true,
            compresslevel = compressed,
            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

    pdfsharedobject = function(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

    lpdf.flushobject          = pdfflushobject
    lpdf.flushstreamobject    = pdfflushstreamobject
    lpdf.shareobjectreference = pdfsharedobject
    lpdf.sharedobject         = pdfsharedobject

end

local preallocated = false -- true will not preallocate

local pages  do

    local texgetcount = tex.getcount

    pages = setmetatableindex(function(t,k)
        if not preallocated then
            local n = structures.counters.record("realpage")["last"]
            for i=1,n do
                t[i] = pdfreserveobject()
            end
            preallocated = true
            if k <= n then
                return t[k]
            end
        end
        local v = pdfreserveobject()
        t[k] = v
        return v
    end)

    pdfgetpagereference = function(n)
        return pages[n]
    end

    lpdf.getpagereference = pdfgetpagereference

end


local function flushnormalobj(data,n)
    if not n then
        nofobjects = nofobjects + 1
        n = nofobjects
    end
    if encryptobject then
        data = encryptobject(data)
    end
    data = f_object(n,data)
    if level == 0 then
        objects[n] = offset
        offset = offset + #data
        flush(f,data)
    else
        if not lastdeferred then
            lastdeferred = n
        elseif n < lastdeferred then
            lastdeferred = n
        end
        objects[n] = data
    end
    return n
end

local flushstreamobj, streamstatus do

    local uncompressed  = 0
    local compressed    = 0
    local notcompressed = 0

    local threshold     = 40 -- also #("/Filter /FlateDecode") (compression threshold)

    -- directives.register("backend.pdf.threshold",function(v)
    --     if v then
    --         threshold = tonumber(v) or 40
    --     else
    --         threshold = -1000
    --     end
    -- end)

    streamstatus = function()
        return {
            nofstreams          = uncompressed + compressed + notcompressed,
            uncompressed        = uncompressed,
            compressed          = compressed,
            notcompressed       = notcompressed,
            threshold           = threshold,
            compresslevel       = lpdf.compresslevel(),
            objectcompresslevel = lpdf.objectcompresslevel(),

        }
    end

    local function updatelength(d,size)
        -- qpdf wants + 1 but acrobat crashes
        d = gsub(d,"/Length %s*%d+%s*",        "/Length " .. size .. " ")
        -- this can happen when we copy
        d = gsub(d,"(/Length %d+ )%d+ R%s*", "%1")
        return d
    end

    flushstreamobj = function(data,n,dict,comp,nolength)
        if not data then
            report("no data for %S",dict)
            return
        end
        if not n then
            nofobjects = nofobjects + 1
            n = nofobjects
        end
        local size = #data
        if comp ~= false then
            comp = compress and size > threshold
        end
        if encryptobject then
            dict = encryptobject(dict)
        end
        if dict == "" then
            dict = nil
        end
        -- level is output nesting level
        if level == 0 then
            local b = nil
            local e = s_stream_e
            if nolength then
                b = f_stream_b_d_r(n,dict) -- raw object, already treated
                if encryptstream then
                 -- print("check length, case 2")
                    data = encryptstream(data)
                    size = #data
                end
                b = updatelength(b,size)
                uncompressed = uncompressed + 1
            else
                if comp then
                    local compdata = compressdata(data,size)
                    if compdata then
                        local compsize = #compdata
                        if compsize <= size - threshold then
                            data = compdata
                            size = compsize
                        else
                            comp = false
                        end
                    else
                        comp = false
                    end
                end
                if encryptstream then
                    data = encryptstream(data)
                    size = #data
                end
                if comp then
                    b = dict and f_stream_b_d_c(n,dict,size) or f_stream_b_n_c(n,size)
                    compressed = compressed + 1
                else
                    b = dict and f_stream_b_d_u(n,dict,size) or f_stream_b_n_u(n,size)
                    notcompressed = notcompressed + 1
                end
            end
            flush(f,b)
            flush(f,data)
            flush(f,e)
            objects[n] = offset
            offset = offset + #b + size + #e
        else
            if nolength then
                if encryptstream then
                 -- print("check length, case 1")
                    data = encryptstream(data)
                    size = #data
                end
                dict = updatelength(dict,size)
                data = f_stream_d_r(n,dict,data) -- raw object, already treated
                uncompressed = uncompressed + 1
            else
                if comp then
                    local compdata = compressdata(data,size)
                    if compdata then
                        local compsize = #compdata
                        if compsize <= size - threshold then
                            data = compdata
                            size = compsize
                        else
                            comp = false
                        end
                    else
                        comp = false
                    end
                end
                if encryptstream then
                    data = encryptstream(data)
                    size = #data
                end
                if comp then
                    data = dict and f_stream_d_c(n,dict,size,data) or f_stream_n_c(n,size,data)
                    compressed = compressed + 1
                else
                    data = dict and f_stream_d_u(n,dict,size,data) or f_stream_n_u(n,size,data)
                    notcompressed = notcompressed + 1
                end
            end
            if not lastdeferred then
                lastdeferred = n
            elseif n < lastdeferred then
                lastdeferred = n
            end
            objects[n] = data
        end
        return n
    end

end

flushdeferred = function() -- was forward defined
    if lastdeferred then
        for n=lastdeferred,nofobjects do
            local o = objects[n]
            if type(o) == "string" then
                objects[n] = offset
                offset = offset + #o
                flush(f,o)
            end
        end
        lastdeferred = false
    end
end

pdfimmediateobject = function(a,b,c,d)
    local kind --, immediate
    local objnum, data, attr, filename
    local compresslevel, objcompression, nolength
    local argtype = type(a)
    if argtype == "table" then
        kind           = a.type          -- raw | stream
     -- immediate      = a.immediate
        objnum         = a.objnum
        attr           = a.attr
        compresslevel  = a.compresslevel
        objcompression = a.objcompression
        filename       = a.file
        data           = a.string or a.stream or ""
        nolength       = a.nolength
        if kind == "stream" then
            if filename then
                data = loaddata(filename) or ""
            end
        elseif kind == "raw" then
            if filename then
                data = loaddata(filename) or ""
            end
        elseif kind == "file"then
            kind = "raw"
            data = filename and loaddata(filename) or ""
        elseif kind == "streamfile" then
            kind = "stream"
            data = filename and loaddata(filename) or ""
        end
    else
        if argtype == "number" then
            objnum = a
            a, b, c = b, c, d
        else
            nofobjects = nofobjects + 1
            objnum = nofobjects
        end
        if b then
            if a == "stream" then
                kind = "stream"
                data = b
            elseif a == "file" then
             -- kind = "raw"
                data = loaddata(b)
            elseif a == "streamfile" then
                kind = "stream"
                data = loaddata(b)
            else
                data = "" -- invalid object
            end
            attr = c
        else
         -- kind = "raw"
            data = a
        end
    end
    if not objnum then
        nofobjects = nofobjects + 1
        objnum = nofobjects
    end
    -- todo: immediate
    if kind == "stream" then
        flushstreamobj(data,objnum,attr,compresslevel,nolength) -- nil == auto
    elseif objectstream and objcompression ~= false then
        addtocache(objnum,data)
    else
        flushnormalobj(data,objnum)
    end
    return objnum
end

pdfdeferredobject    = pdfimmediateobject

lpdf.deferredobject  = pdfimmediateobject
lpdf.immediateobject = pdfimmediateobject

-- In lua 5.4 the methods are now moved one metalevel deeper so we need to get them
-- from mt.__index instead. (I did get that at first.) It makes for a slightly (imo)
-- nicer interface but no real gain in speed as we don't flush that often.

local openfile, closefile  do

    -- I used to do <space><lf> but then figured out that when I open and save a file in a mode
    -- that removes trailing spaces, the xref becomes invalid. The problem was then that a
    -- reconstruction of the file by a viewer gives weird effects probably because percent symbols
    -- gets interpreted then. Thanks to Ross Moore for noticing this side effect!

    local f_used       = formatters["%010i 00000 n\013\010"]
    local f_link       = formatters["%010i 00000 f\013\010"]
    local f_first      = formatters["%010i 65535 f\013\010"]

    local f_pdf_tag    = formatters["%%PDF-%i.%i\010"]
    local f_xref       = formatters["xref\0100 %i\010"]
    local f_trailer_id = formatters["trailer\010<< %s /ID [ <%s> <%s> ] >>\010startxref\010%i\010%%%%EOF"]
    local f_trailer_no = formatters["trailer\010<< %s >>\010startxref\010%i\010%%%%EOF"]
    local f_startxref  = formatters["startxref\010%i\010%%%%EOF"]

    local inmemory = false
    local close    = false
    local update   = false
    local usedname = false
    local usedsize = false

    directives.enable("backend.pdf.inmemory", function(v) inmemory = true end)

 -- local banner <const> = "%\xCC\xD5\xC1\xD4\xC5\xD8\xD0\xC4\xC6\010"     -- LUATEXPDF  (+128)
    local banner <const> = "%\xC3\xCF\xCE\xD4\xC5\xD8\xD4\xD0\xC4\xC6\010" -- CONTEXTPDF (+128)

    openfile = function(filename)
        --
        local arguments = environment.arguments
        if arguments.ownerpassword then
            lpdf.setencryption {
                ownerpassword = arguments.ownerpassword,
                userpassword  = arguments.userpassword,
                permissions   = arguments.permissions,
            }
        end
        --
        if not preallocated then
            local dummy = pages[1]
        end
        --
        if inmemory then
            local n = 0
            f = { }
            flush = function(f,s)
                n = n + 1 f[n] = s
             -- offset = offset + #s
            end
            close = function(f)
                f = concat(f)
                usedsize = #f
                io.savedata(filename,f)
                f = false
            end
            update = function(f,s)
                f[1] = s
            end
         -- local n = 0
         -- f = {
         --     write = function(self,s)
         --         n = n + 1 f[n] = s
         --     end,
         --     close = function(self)
         --         f = concat(f)
         --         io.savedata(filename,f)
         --         f = false
         --     end,
         -- }
         else
            f = io.open(filename,"wb")
            if not f then
                report()
                report("quitting because file %a cannot be opened for writing",filename)
                report()
                os.exit()
            end
         -- f:setvbuf("full",64*1024)
            local m = getmetatable(f)
            flush = m.write or m.__index.write
            close = m.close or m.__index.close
            update = function(f,s)
                f:seek("set",0)
                f:write(s)
            end
        end
        local version = f_pdf_tag(majorversion,minorversion)
        flush(f,version)
        flush(f,banner)
        offset = offset + #version + #banner
        usedname = filename
    end

    local function geterrorfilename()
        if environment.arguments.signal == "qr" then
            if file.nameonly(tex.jobname) == "s-system-error" then
                -- recursing
            else
                -- if really needed we can generate a pdf directly
                local pdfname = tex.jobname .. "-errorstate.pdf"
                table.save("context-lmtx-error.lua",job.errorstate)
             -- local joke = os.execute ("context --global --purgeall --once s-system-error.mkxl --result=" .. pdfname)
                local joke = os.resultof("context --global --purgeall --once s-system-error.mkxl --result=" .. pdfname)
                if lfs.isfile(pdfname) then
                    return pdfname
                end
            end
        end
        return resolvers.findfile("context-lmtx-error.pdf")
    end

    closefile = function(abort)
        if lpdf.getnofpages() == 0 then
            abort = usedname
        end
        if abort then
            close(f)
            if not environment.arguments.nodummy then
                f = io.open(abort,"wb")
                if f then
                    f:close()
                    local name = geterrorfilename()
                    if name then
                        local data = io.loaddata(name)
                        if data then
                            f = io.open(abort,"wb")
                            f:write(data)
                            f:close()
                            return
                        end
                    end
                end
            end
            os.remove(abort)
        else
            local xrefoffset = offset
            local lastfree   = 0
            local noffree    = 0
            --
            local os = objectstream
            if encryptstream then
                objectstream = false
            end
            local catalog = lpdf.getcatalog()
            objectstream = os
            --
            local info       = lpdf.getinfo()
            local trailerid  = lpdf.gettrailerid()
            --
            if objectstream then
                flushdeferred()
                flushcache()
                --
                offset = lpdf.preparesignature and lpdf.preparesignature(flush,f,offset,objects) or offset
                --
                xrefoffset = offset
                --
                nofobjects = nofobjects + 1
                objects[nofobjects] = offset -- + 1
                --
                -- combine these three in one doesn't really give less code so
                -- we go for the efficient ones
                --
                local nofbytes  = 4
                local c1, c2, c3, c4
                if offset <= 0xFFFF then
                    nofbytes = 2
                    for i=1,nofobjects do
                        local o = objects[i]
                        if not o then
                            noffree = noffree + 1
                        else
                            local strm = o < 0
                            if strm then
                                o = -o
                            end
                            c1 = (o>>8)&0xFF
                            c2 = (o>>0)&0xFF
                            if strm then
                                objects[i] = char(2,c1,c2,streams[o][i])
                            else
                                objects[i] = char(1,c1,c2,0)
                            end
                        end
                    end
                    if noffree > 0 then
                        for i=nofobjects,1,-1 do
                            local o = objects[i]
                            if not o then
                                local f1 = (lastfree>>8)&0xFF
                                local f2 = (lastfree>>0)&0xFF
                                objects[i] = char(0,f1,f2,0)
                                lastfree   = i
                            end
                        end
                    end
                elseif offset <= 0xFFFFFF then
                    nofbytes = 3
                    for i=1,nofobjects do
                        local o = objects[i]
                        if not o then
                            noffree = noffree + 1
                        else
                            local strm = o < 0
                            if strm then
                                o = -o
                            end
                            c1 = (o>>16)&0xFF
                            c2 = (o>> 8)&0xFF
                            c3 = (o>> 0)&0xFF
                            if strm then
                                objects[i] = char(2,c1,c2,c3,streams[o][i])
                            else
                                objects[i] = char(1,c1,c2,c3,0)
                            end
                        end
                    end
                    if noffree > 0 then
                        for i=nofobjects,1,-1 do
                            local o = objects[i]
                            if not o then
                                local f1 = (lastfree>>16)&0xFF
                                local f2 = (lastfree>> 8)&0xFF
                                local f3 = (lastfree>> 0)&0xFF
                                objects[i] = char(0,f1,f2,f3,0)
                                lastfree   = i
                            end
                        end
                    end
                else
                    nofbytes = 4
                    for i=1,nofobjects do
                        local o = objects[i]
                        if not o then
                            noffree = noffree + 1
                        else
                            local strm = o < 0
                            if strm then
                                o = -o
                            end
                            c1 = (o>>24)&0xFF
                            c2 = (o>>16)&0xFF
                            c3 = (o>> 8)&0xFF
                            c4 = (o>> 0)&0xFF
                            if strm then
                                objects[i] = char(2,c1,c2,c3,c4,streams[o][i])
                            else
                                objects[i] = char(1,c1,c2,c3,c4,0)
                            end
                        end
                    end
                    if noffree > 0 then
                        for i=nofobjects,1,-1 do
                            local o = objects[i]
                            if not o then
                                local f1 = (lastfree>>24)&0xFF
                                local f2 = (lastfree>>16)&0xFF
                                local f3 = (lastfree>> 8)&0xFF
                                local f4 = (lastfree>> 0)&0xFF
                                objects[i] = char(0,f1,f2,f3,f4,0)
                                lastfree   = i
                            end
                        end
                    end
                end
                objects[0] = rep("\0",1+nofbytes+1)
                local data = concat(objects,"",0,nofobjects)
                local size = #data
                local xref = pdfdictionary {
                    Type    = pdfconstant("XRef"),
                    Size    = nofobjects + 1,
                    W       = pdfarray { 1, nofbytes, 1 },
                    Root    = catalog,
                    Info    = info,
                    ID      = trailerid and pdfarray { pdfliteral(trailerid,true), pdfliteral(trailerid,true) } or nil,
                    Encrypt = encdict or nil,
                }
                local fb
             -- if encryptstream then
             --     if compress then
             --         local comp = compressdata(data,size)
             --         if comp then
             --             data = comp
             --             size = #data
             --             fb = f_stream_b_d_c
             --             xref.Filter = pdfarray {
             --                 pdfconstant("Crypt"), -- identity
             --                 pdfconstant("FlateDecode")
             --             }
             --         else
             --             xref.Filter = pdfconstant("Crypt") -- identity
             --         end
             --     else
             --         xref.Filter = pdfconstant("Crypt") -- identity
             --     end
             --     fb = f_stream_b_d_u
             -- else
                    if compress then
                        local comp = compressdata(data,size)
                        if comp then
                            data = comp
                            size = #data
                            fb = f_stream_b_d_c
                        else
                            fb = f_stream_b_d_u
                        end
                    else
                        fb = f_stream_b_d_u
                    end
             -- end
                -- no encryption of data here
                flush(f,fb(nofobjects,xref(),size))
                flush(f,data)
                flush(f,s_stream_e)
                flush(f,f_startxref(xrefoffset))
            else
                flushdeferred()
                --
                offset = lpdf.preparesignature and lpdf.preparesignature(flush,f,offset,objects) or offset
                --
             -- if encryptstream then
             --     -- unencrypted !
             --     local eo = encryptobject
             --     encryptobject = false
             --     encdict = pdfreference(pdfimmediateobject(tostring(encdict)))
             --     encryptobject = eo
             -- end
                --
                xrefoffset = offset
                flush(f,f_xref(nofobjects+1))
                local trailer = pdfdictionary {
                    Size    = nofobjects + 1,
                    Root    = catalog,
                    Info    = info,
                    Encrypt = encdict or nil,
                }
                for i=1,nofobjects do
                    local o = objects[i]
                    if o then
-- if o < 0 then
--     o = -o
-- end
                       objects[i] = f_used(o)
                    end
                end
                for i=nofobjects,1,-1 do
                    local o = objects[i]
                    if not o then
                        objects[i] = f_link(lastfree)
                        lastfree   = i
                    end
                end
                objects[0] = f_first(lastfree)
                flush(f,concat(objects,"",0,nofobjects))
                trailer.Size = nofobjects + 1
                if trailerid then
                    flush(f,f_trailer_id(trailer(),trailerid,trailerid,xrefoffset))
                else
                    flush(f,f_trailer_no(trailer(),xrefoffset))
                end
            end
            update(f,f_pdf_tag(majorversion,minorversion)) -- check this
            usedsize = f:seek("end")
            close(f)
            io.flush()
            if lpdf.finalizesignature then lpdf.finalizesignature(usedname,usedsize) end
        end
        io.flush()
        closefile = function() end
    end

end

-- For the moment we overload it here, although back-fil.lua eventually will
-- be merged with back-pdf as it's pdf specific, or maybe back-imp-pdf or so.

do

    -- We overload img but at some point it will even go away, so we just
    -- reimplement what we need in context. This will change completely i.e.
    -- we will drop the low level interface!

    local pdfbackend     = backends.registered.pdf
    local nodeinjections = pdfbackend.nodeinjections
    local codeinjections = pdfbackend.codeinjections

    local imagetypes     = images.types -- pdf png jpg jp2 jbig2 stream
    local img_none       = imagetypes.none

    local tonode         = nuts.tonode
    local newimagerule   = nuts.pool.imagerule
    local setattrlist    = nuts.setattrlist
    local setprop        = nuts.setprop

    local report_images  = logs.reporter("backend","images")

    local lastindex      = 0
    local indices        = { }

    local bpfactor       = number.dimenfactors.bp

    function codeinjections.newimage(specification)
        return specification
    end

    function codeinjections.copyimage(original)
        return setmetatableindex(original)
    end

    function codeinjections.scanimage(specification)
        -- placeholder, doesn't give back dimensions etc but will be plugged in
        return specification
    end

    local function embedimage(specification)
        if specification then
            lastindex = lastindex + 1
            specification.index = lastindex
            local xobject = pdfdictionary { }
            if not specification.notype then
                xobject.Type     = pdf_xobject
                xobject.Subtype  = pdf_form
                xobject.FormType = 1
            end
            local bbox = specification.bbox
            if bbox and not specification.nobbox then
                xobject.BBox = pdfarray {
                    bbox[1] * bpfactor,
                    bbox[2] * bpfactor,
                    bbox[3] * bpfactor,
                    bbox[4] * bpfactor,
                }
            end
            xobject = xobject + specification.attr
            if bbox and not specification.width then
                specification.width = bbox[3]
            end
            if bbox and not specification.height then
                specification.height = bbox[4]
            end
            local dict = xobject()
            --
            nofobjects     = nofobjects + 1
            local objnum   = nofobjects
            local nolength = specification.nolength
            local stream   = specification.stream or specification.string
            --
            -- We cannot set type in native img so we need this hack or
            -- otherwise we need to patch too much. Better that i write
            -- a wrapper then. Anyway, it has to be done better: a key that
            -- tells either or not to scale by xsize/ysize when flushing.
            --
            if not specification.type then
                local kind = specification.kind
                if kind then
                    -- take that one
                elseif attr and find(attr,"BBox") then
                    kind = img_stream
                else
                    -- hack: a bitmap
                    kind = img_none
                end
                specification.type = kind
                specification.kind = kind
            end
            flushstreamobj(stream,objnum,dict,compresslevel,nolength)
            specification.objnum      = objnum
            specification.rotation    = specification.rotation or 0
            specification.orientation = specification.orientation or 0
            specification.transform   = specification.transform or 0
            specification.stream      = nil
            specification.attr        = nil
            specification.type        = specification.kind or specification.type or img_none
            indices[lastindex]        = specification -- better create a real specification
            return specification
        end
    end

    codeinjections.embedimage = embedimage

    function codeinjections.wrapimage(specification)
        --
        local index = specification.index
        if not index then
            embedimage(specification)
        end
        --
        local n = newimagerule(
            specification.width  or 0,
            specification.height or 0,
            specification.depth  or 0
        )
        setattrlist(n,true)
        setprop(n,"index",specification.index)
        return tonode(n)
    end

    pdfincludeimage = function(index)
        local specification = indices[index]
        if specification then
            local bbox      = specification.bbox
            local xorigin   = bbox[1]
            local yorigin   = bbox[2]
            local xsize     = bbox[3] - xorigin -- we need the original ones, not the 'rotated' ones
            local ysize     = bbox[4] - yorigin -- we need the original ones, not the 'rotated' ones
            local transform = specification.transform or 0
            local objnum    = specification.objnum or pdfreserveobject()
            local groupref  = nil
            local kind      = specification.kind or specification.type or img_none -- determines scaling type
            return
                kind,
                xorigin, yorigin,
                xsize, ysize,
                transform,
                objnum,
                groupref
        end
    end

    lpdf.includeimage = pdfincludeimage

end

-- The driver.

do

 -- local addsuffix  = file.addsuffix
    local texgetbox  = tex.getbox

    local pdfname    = nil
    local converter  = nil
    local useddriver = nil -- a bit of a hack

    local function outputfilename(driver)
        return pdfname
    end

-- local outputfilename ; do -- old todo usedname in ^^
--     local filename = nil
--     outputfilename = function(driver,usedname)
--         if usedname and usedname ~= "" then
--             filename = addsuffix(usedname,"pdf")
--         elseif not filename or filename == "" then
--             filename = addsuffix(tex.jobname,"pdf")
--         end
--         return filename
--     end
-- end

    -- todo: prevent twice
    local function prepare(driver)
        if not environment.initex then
            --
            backends.initialize("pdf") -- also does bindings
            --
            pdfname = tex.jobname .. ".pdf"
            openfile(pdfname)
            --
            luatex.registerstopactions(1,function()
                if pdfname then
                    lpdf.finalizedocument()
                    closefile()
                    pdfname = nil
                end
            end)
            --
            luatex.registerpageactions(1,function()
                if pdfname then
                    lpdf.finalizepage(true)
                end
            end)
            --
            lpdf.registerdocumentfinalizer(wrapupdocument,nil,"wrapping up")
            --
            statistics.register("result saved in file", function()
                local status = streamstatus()
                local outputfilename = environment.outputfilename or environment.jobname or tex.jobname or "<unset>"
                outputfilename = gsub(outputfilename,"^%./+","") -- todo: make/use a helper
                return string.format(
                    "%s.pdf, compresslevel %s, objectcompresslevel %s, %i streams, %i uncompressed, %i compressed, %i not compressed, threshold %i",
                    outputfilename,
                    status.compresslevel,
                    status.objectcompresslevel,
                    status.nofstreams or 0,
                    status.uncompressed or 0,
                    status.compressed or 0,
                    status.notcompressed or 0,
                    status.threshold or 0
                )
            end)
            --
            luatex.registerstopactions(function()
                if pdfname then
                    local r = lpdf.lastreferredpage() -- somehow referenced
                    local s = lpdf.getnofpages()      -- in page tree, saved in file
                    local t = lpdf.nofpages()         -- in tuc file
                    if r > s then
                        report()
                        report("referred pages: %i, saved pages %i, pages from tuc file: %i, possible corrupt file",r,s,t)
                        report()
                    end
                end
            end)
        end
        converter  = drivers.converters.lmtx
        useddriver = driver
    end

    local function wrapup(driver)
        if pdfname then
            closefile()
            pdfname = nil
        end
    end

    local function cleanup(driver)
        if pdfname then
            closefile(pdfname)
            pdfname = nil
        end
    end

    local function convert(driver,boxnumber)
        converter(driver,texgetbox(boxnumber),"page")
    end

--     localconverter = function(...)
--         print(...)                -- ok when we add this
--         converter(useddriver,...) -- otherwise nil .. lua bug
--     end

    localconverter = function(a,b,c,d)
        converter(useddriver,a,b,c,d)
    end

    drivers.install {
        name     = "pdf",
        flushers = flushers,
        actions  = {
            prepare         = prepare,
            wrapup          = wrapup,
            cleanup         = cleanup,
            --
            initialize      = initialize,
            convert         = convert,
            finalize        = finalize,
            --
            outputfilename  = outputfilename,
        },
    }

end
