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

local gsub = string.gsub
local concat, insert, remove, sortedkeys = table.concat, table.insert, table.remove, table.sortedkeys
local sqrt, round = math.sqrt, math.round
local setmetatable, rawset, tostring, tonumber, type = setmetatable, rawset, tostring, tonumber, type
local P, S, C, Ct, Cc, Cg, Cf, Carg = lpeg.P, lpeg.S, lpeg.C, lpeg.Ct, lpeg.Cc, lpeg.Cg, lpeg.Cf, lpeg.Carg
local lpegmatch = lpeg.match
local formatters = string.formatters

local report_metapost = logs.reporter("metapost")

local trace_variables = false  trackers.register("metapost.variables",function(v) trace_variables = v end)

local mplib             = mplib
local context           = context

local allocate          = utilities.storage.allocate

local getfields         = mplib.getfields or mplib.fields -- todo: in lmtx get them once and then use gettype
local getobjectdata     = mplib.getobjectdata
local getobjectstacking = mplib.getobjectstacking

local save_table        = false
local force_stroke      = false

metapost                = metapost or { }
local metapost          = metapost

metapost.flushers       = metapost.flushers or { }
local pdfflusher        = { }
metapost.flushers.pdf   = pdfflusher

metapost.n              = 0

----- state_regular <const> = 0
local state_begin   <const> = 1
local state_end     <const> = 2
local state_single  <const> = 3

local mpsliteral        = nodes.pool.originliteral

local f_f  = formatters["%.6N"]
local f_m  = formatters["%.6N %.6N m"]
local f_c  = formatters["%.6N %.6N %.6N %.6N %.6N %.6N c"]
local f_l  = formatters["%.6N %.6N l"]
local f_cm = formatters["%.6N %.6N %.6N %.6N %.6N %.6N cm"]
local f_M  = formatters["%.6N M"]
local f_j  = formatters["%i j"]
local f_J  = formatters["%i J"]
local f_d  = formatters["[%s] %.6N d"]
local f_w  = formatters["%.6N w"]
local f_r  = formatters["%.6N %.6N %.6N %.6N re"]

directives.register("metapost.savetable",function(v)
    if type(v) == "string" then
        save_table = file.addsuffix(v,"mpl")
    elseif v then
        save_table = file.addsuffix(environment.jobname .. "-graphic","mpl")
    else
        save_table = false
    end
end)

trackers.register("metapost.forcestroke",function(v)
    force_stroke = v
end)

-- local function gettolerance(objects)
--     return objects:tolerance()
-- end

function metapost.convert(specification,result)
    local flusher  = specification.flusher
    local askedfig = specification.askedfig
    if save_table then
        table.save(save_table,metapost.totable(result,1)) -- direct
    end
    metapost.flush(specification,result)
    return true -- done
end

function pdfflusher.tocomment(message)
    if message then
        return formatters["%% mps graphic %s: %s"](metapost.n,message)
    else
        return formatters["%% mps graphic %s"](metapost.n)
    end
end

-- Here timing is tricky: this is a delayed command so we should register that
-- in a nested context function call but ... we can actually expand the
-- macro directly.

-- function pdfflusher.startfigure(n,llx,lly,urx,ury,message,figure,plugmode)
--     metapost.n = metapost.n + 1
--     -- context(... start timing ...)
--     context.startMPLIBtoPDF(f_f(llx),f_f(lly),f_f(urx),f_f(ury),plugmode and "1" or "0")
-- end
--
-- function pdfflusher.stopfigure(message)
--     context.stopMPLIBtoPDF()
--     -- context(... stop timing ...)
-- end

local expandmacro = token.expandmacro

function pdfflusher.startfigure(n,llx,lly,urx,ury,message,figure,plugmode)
    metapost.n = metapost.n + 1
    expandmacro("startMPLIBtoPDF",
        true,f_f(llx),
        true,f_f(lly),
        true,f_f(urx),
        true,f_f(ury),
        true,plugmode and "1" or "0"
    )
end

function pdfflusher.stopfigure(message)
    expandmacro("stopMPLIBtoPDF")
end

-- pdfliterals = "/Artifact BMC\n" .. pdfliterals .. "\n" .. "EMC"

function pdfflusher.flushfigure(pdfliterals) -- table
    if #pdfliterals > 0 then
        pdfliterals = concat(pdfliterals,"\n")
        context(mpsliteral(pdfliterals))
    end
end

function pdfflusher.textfigure(font,size,text,width,height,depth) -- we could save the factor
    text = gsub(text,".","\\hbox{%1}") -- kerning happens in metapost (i have to check if this is true for mplib)
    context.MPtextext(font,size,text,0,-number.dimenfactors.bp*depth)
end

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

local function pen_characteristics(peninfo)
    rx = peninfo.rx
    ry = peninfo.ry
    sx = peninfo.sx
    sy = peninfo.sy
    tx = peninfo.tx
    ty = peninfo.ty
    divider = sx*sy - rx*ry
    return not (sx == 1 and rx == 0 and ry == 0 and sy == 1 and tx == 0 and ty == 0), peninfo.width
end

local function mpconcat(px, py) -- no tx, ty here / we can move this one inline if needed
    return (sy*px-ry*py)/divider,(sx*py-rx*px)/divider
end

local getbendtolerance = metapost.getbendtolerance
local curved           = metapost.hascurvature

local function flushrectangularpath(path,t)
    if not t then
        t = { }
    end
    --
    local p1 = path[1]
    local p3 = path[3]
    local p1x = p1.x_coord
    local p1y = p1.y_coord
    local p3x = p3.x_coord
    local p3y = p3.y_coord
    --
    t[#t+1] = f_r(p1x,p1y,p3x-p1x,p3y-p1y)
    return t
end

local function flushrectangularlist(path,t)
    if not t then
        t = { }
    end
    --
    local p1x = p[1]
    local p1y = p[2]
    local p3x = p[5]
    local p3y = p[6]
    --
    t[#t+1] = f_r(p1x,p1y,p3x-p1x,p3y-p1y)
    return t
end

local optimize_lines = false  experiments.register("metapost.optimizelines", function(v) optimize_lines = v end)

local function flushnormalpath(path,t,open,tolerance,reduce)
    local pth, ith, nt
    local length = #path
    if t then
        nt = #t
    else
        t = { }
        nt = 0
    end
    if reduce and length == 4 and path.cycle then
        local p1 = path[1]
        local p2 = path[2]
        local p3 = path[3]
        local p4 = path[4]
        if not p1.curved and not p2.curved and not p3.curved and not p4.curved then
            local p1x = p1.x_coord
            local p1y = p1.y_coord
            local p3x = p3.x_coord
            local p3y = p3.y_coord
            if p1y == p2.y_coord and p1x == p4.x_coord and p2.x_coord == p3x and p3y == p4.y_coord then
                nt = nt + 1
                t[nt] = f_r(p1x,p1y,p3x-p1x,p3y-p1y)
                path.rectangle = true
                return t
            end
        end
    end
    if optimize_lines then -- unchecked for single
        -- todo : use direction so that we can also do slopes
        local lastx  = false
        local lasty  = false
        local lastm  = false
        local lastd  = false
        local lastdx = false
        local lastdy = false
        for i=1,length do
            pth = path[i]
            local x = pth.x_coord
            local y = pth.y_coord
            local state = pth.state
            if not ith or state == state_begin or state == state_single then
                nt = nt + 1
                t[nt] = f_m(x,y)
                lastx = x
                lasty = y
                if state == state_single then
                    nt = nt + 1
                    t[nt] = f_l(x,y)
                    lastm = true -- check this
                    lastdx = "stay"
                    lastdy = "stay"
                else
                    lastm = true
                    lastdx = "move"
                    lastdy = "move"
                end
            elseif pth.curved then -- curved(ith,pth,tolerance)
                nt = nt + 1
                t[nt] = f_c(ith.right_x,ith.right_y,pth.left_x,pth.left_y,x,y)
                lastx = false
                lasty = false
                lastm = false
            elseif lastm  then
                local dx, dy
                if x > lastx then
                    dx = "right"
                elseif x < lastx then
                    dx = "left"
                else
                    dx = "stay"
                end
                if y > lasty then
                    dy = "up"
                elseif y < lasty then
                    dy = "down"
                else
                    dy = "stay"
                end
                if dy == "stay" and lastdx == dx then
                    t[nt] = f_l(x,y)
                    lastx = x
                elseif dx == "stay" and lasty == dy then
                    t[nt] = f_l(x,y)
                    lasty = y
                else
                    nt = nt + 1
                    t[nt] = f_l(x,y)
                    lastx = x
                    lasty = y
                end
                lastdx = dx
                lastdy = dy
            else
                nt = nt + 1
                t[nt] = f_l(x,y)
                lastx = x
                lasty = y
            end
            ith = pth
        end
        -- also below
    else
        for i=1,length do
            nt = nt + 1
            pth = path[i]
            local state = pth.state
            local x     = pth.x_coord
            local y     = pth.y_coord
            if not ith or state == state_begin or state == state_single then
                t[nt] = f_m(x,y)
                if state == state_single then
                    nt = nt + 1
                    t[nt] = f_l(x,y)
                end
            elseif pth.curved then -- curved(ith,pth,tolerance)
                t[nt] = f_c(ith.right_x,ith.right_y,pth.left_x,pth.left_y,x,y)
            else
                t[nt] = f_l(x,y)
            end
            ith = pth
        end
    end
    if not open then
        nt = nt + 1
        local one = path[1]
        local len = path[length]
        if len.state == state_end then
            -- special case (potrace-001.tex)
        else
            local state = one.state
            local x = one.x_coord
            local y = one.y_coord
            if state == state_begin then
                t[nt] = f_m(x,y)
            elseif state == state_single then
                t[nt] = f_m(x,y)
                nt = nt + 1
                t[nt] = f_l(x,y)
            elseif one.curved then -- curved(pth,one,tolerance)
                t[nt] = f_c(pth.right_x,pth.right_y,one.left_x,one.left_y,x,y)
            else
                t[nt] = f_l(x,y)
            end
        end
    elseif length == 1 then
        -- special case .. draw point
        local one = path[1]
        nt = nt + 1
        t[nt] = f_l(one.x_coord,one.y_coord)
    end
    return t
end

local function flushnormallist(path,length,t,open)
    local n = #t
    local x = path[1]
    local y = path[2]
    n = n + 1 ; t[n] = f_m(x,y)
    for i=3,length,2 do
        n = n + 1 ; t[n] = f_l(path[i],path[i+1])
    end
 -- if not open or length == 2 then -- also handles dot
    if length == 2 then -- dot
        n = n + 1 ; t[nt] = f_l(x,y)
    end
end

local function flushconcatpath(path,t,open,tolerance,transform)
    local pth, ith, nt
    local length = #path
    if t then
        nt = #t
    else
        t = { }
        nt = 0
    end
    if transform then
        nt = nt + 1
        t[nt] = f_cm(sx,rx,ry,sy,tx,ty)
    end
    for i=1,length do
        nt = nt + 1
        pth = path[i]
        local state = pth.state
        -- here concat for all x y 
        local x, y  = mpconcat(pth.x_coord,pth.y_coord)
        if not ith or state == state_begin or state == state_single then
            t[nt] = f_m(x,y)
            if state == state_single then
                nt = nt + 1
                t[nt] = f_l(x,y)
            end
        elseif pth.curved then -- curved(ith,pth,tolerance)
            local a, b = mpconcat(ith.right_x,ith.right_y)
            local c, d = mpconcat(pth.left_x,pth.left_y)
            t[nt] = f_c(a,b,c,d,x,y)
        else
           t[nt] = f_l(x,y)
        end
        ith = pth
    end
    if not open then
        nt = nt + 1
        local len = path[length]
        if len.state == state_end then
            -- special case (potrace-001.tex)
        else
            local one   = path[1]
            local state = one.state
            local x     = one.x_coord
            local y     = one.y_coord
            -- here concat for some x y ... 
            if state == state_begin then
                t[nt] = f_m(x,y)
            elseif state == state_single then
                t[nt] = f_m(x,y)
                nt = nt + 1
                t[nt] = f_l(x,y)
            else
                -- ... like here:
                x, y = mpconcat(x,y)
                if one.curved then -- curved(pth,one,tolerance)
                    local a, b = mpconcat(pth.right_x,pth.right_y)
                    local c, d = mpconcat(one.left_x,one.left_y)
                    t[nt] = f_c(a,b,c,d,x,y)
                else
                    t[nt] = f_l(x,y)
                end
            end
        end
    elseif length == 1 then
        -- special case .. draw point
        nt = nt + 1
        local one = path[1]
        t[nt] = f_l(mpconcat(one.x_coord,one.y_coord))
    end
    return t
end

local function toboundingbox(path)
    local size = #path
    if size == 4 then
        local pth = path[1]
        local x = pth.x_coord
        local y = pth.y_coord
        local llx, lly, urx, ury = x, y, x, y
        for i=2,size do
            pth = path[i]
            x   = pth.x_coord
            y   = pth.y_coord
            if x < llx then
                llx = x
            elseif x > urx then
                urx = x
            end
            if y < lly then
                lly = y
            elseif y > ury then
                ury = y
            end
        end
        return { llx, lly, urx, ury }
    else
        return { 0, 0, 0, 0 }
    end
end

function metapost.flushnormalpath(path,t,open,tolerance)
    return flushnormalpath(path,t,open,tolerance or getbendtolerance(),true)
end

-- The flusher is pdf based, if another backend is used, we need to overload the
-- flusher; this is beta code, the organization will change (already upgraded in
-- sync with mplib)
--
-- We can avoid the before table but I like symmetry. There is of course a small
-- performance penalty, but so is passing extra arguments (result, flusher, after)
-- and returning stuff.
--
-- This variable stuff will change in lmtx.

local ignore   = function() end

local space    = P(" ")
local equal    = P("=")
local key      = C((1-equal)^1) * equal
local newline  = S("\n\r")^1
local number   = (((1-space-newline)^1) / tonumber) * (space^0)

local p_number  = number
local p_string  = C((1-newline)^0)
local p_boolean = P("false") * Cc(false) + P("true") * Cc(true)
local p_set     = Ct(number^1)
local p_path    = Ct(Ct(number * number^-5)^1)

local variable =
    P("1:")            * p_number
  + P("2:")            * p_string
  + P("3:")            * p_boolean
  + S("4568") * P(":") * p_set
  + P("7:")            * p_path

local pattern_tab = Cf ( Carg(1) * (Cg(variable * newline^0)^0), rawset)

local variable =
    P("1:")            * p_number
  + P("2:")            * p_string
  + P("3:")            * p_boolean
  + S("4568") * P(":") * number^1
  + P("7:")            * (number * number^-5)^1

local pattern_lst = (variable * newline^0)^0

metapost.variables  = { } -- currently across instances
metapost.properties = { } -- to be stacked

function metapost.untagvariable(str,variables) -- will be redone
    if variables == false then
        return lpegmatch(pattern_lst,str)
    else
        return lpegmatch(pattern_tab,str,1,variables or { })
    end
end

-- function metapost.processspecial(str)
--     lpegmatch(pattern_key,object.prescript,1,variables)
-- end

-- function metapost.processspecial(str)
--     local code = loadstring(str)
--     if code then
--         if trace_variables then
--             report_metapost("executing special code: %s",str)
--         end
--         code()
--     else
--         report_metapost("invalid special code: %s",str)
--     end
-- end

local stack      = { }   -- general stack (not related to stacking)
local nostacking = { 0 } -- layers in figures

local function pushproperties(figure)
    -- maybe there will be getters in lmtx
    local boundingbox = figure:boundingbox()
    local slot = figure:charcode() or 0
    local properties = {
        llx    = boundingbox[1],
        lly    = boundingbox[2],
        urx    = boundingbox[3],
        ury    = boundingbox[4],
        slot   = slot,
        width  = figure:width(),
        height = figure:height(),
        depth  = figure:depth(),
        italic = figure:italic(),
        number = slot,
    }
    insert(stack,properties)
    metapost.properties = properties
    return properties
end

local function popproperties()
    metapost.properties = remove(stack)
end

local optimizecolor = false

directives.register("metapost.optimizecolor", function(v)
    optimizecolor = v
end)

function metapost.flush(specification,result)
    if result then
        local flusher   = specification.flusher
        local askedfig  = specification.askedfig
        local incontext = specification.incontext
        local filtering = specification.filtering
        local figures   = result.fig
        if figures then
            flusher = flusher or pdfflusher
            local plugmode           = not metapost.getbackendoption(specification.mpx,"noplugins")
            local resetplugins       = plugmode and metapost.resetplugins       or ignore -- before figure
            local processplugins     = plugmode and metapost.processplugins     or ignore -- each object
            local synchronizeplugins = plugmode and metapost.synchronizeplugins or ignore
            local pluginactions      = plugmode and metapost.pluginactions      or ignore -- before / after
            local startfigure        = flusher.startfigure
            local stopfigure         = flusher.stopfigure
            local flushfigure        = flusher.flushfigure
            local textfigure         = flusher.textfigure
         -- local processspecial     = flusher.processspecial or metapost.processspecial
            local tocomment          = flusher.tocomment
            if type(filtering) ~= "table" or not next(filtering) then
                filtering = false
            end
            -- patterns: we always use image 1 and then can use patterns for 2..n (or one number)
            -- we can then do an intermediate flush
            for index=1,#figures do
                local figure     = figures[index]
                local properties = pushproperties(figure)
                if askedfig == "direct" or askedfig == "all" or askedfig == properties.number then
                    local stacking   = figure:stacking() -- This has to happen before fetching objects!
                    local objects    = figure:objects()
                    local tolerance  = figure:tolerance() or getbendtolerance()
                    local result     = { }
                    local miterlimit = -1
                    local linecap    = -1
                    local linejoin   = -1
                    local repeater   = { }
                    local repeated   = 0
                    local dashed     = false
                    local linewidth  = false
                    local llx        = properties.llx
                    local lly        = properties.lly
                    local urx        = properties.urx
                    local ury        = properties.ury
                    if urx < llx then
                        -- invalid
                        startfigure(properties.number,0,0,0,0,"invalid",figure,plugmode)
                        if tocomment then
                            result[#result+1] = tocomment("invalid")
                        end
                        stopfigure()
                    else

                        -- we need to be indirect if we want the one-pass solution

                        local groupstack = { }

                        local function processfigure()
                            if tocomment then
                                result[#result+1] = tocomment("begin")
                            end
                            result[#result+1] = "q"
                            if objects then
                             -- resetplugins(result) -- we should move the colorinitializer here
                                local savedpath = nil
                                local savedhtap = nil
                                if stacking then
                                 -- stacking = { }
                                 -- for o=1,#objects do
                                 --     local stack = objects[o].stacking
                                 --     if stack then
                                 --         if filtering then
                                 --             stacking[stack] = filtering[stack]
                                 --         else
                                 --             stacking[stack] = true
                                 --         end
                                 --     end
                                 -- end
                                    for stack in next, stacking do
                                        if filtering then
                                            stacking[stack] = filtering[stack]
                                        else
                                            stacking[stack] = true
                                        end
                                    end
                                    stacking = sortedkeys(stacking)
                                else
                                    stacking = nostacking
                                end
                                for i=1,#stacking do
                                    -- We now use a direct table instead of a metatabled one. See pre 2025-10-19
                                    -- code for the old, more indirect approach depending on field asccessors.
                                    local stack = stacking[i]
                                    for o=1,#objects do
                                        local object = objects[o]
                                        if stack == getobjectstacking(object) then
                                            object = getobjectdata(object)
                                            local objecttype = object.type
                                            if objecttype == "fill" or objecttype == "outline" then
                                                if object.bytemap then
                                                    -- We trigger the bm plugin. There can be a prescript "bytemask=n" so we
                                                    -- need to preserve that. In that plugin we use object.bytemap directly.
                                                    local bm_trigger = "bytemap=true"
                                                    if object.prescript and object.prescript ~= "" then
                                                        object.prescript = bm_trigger .. "\n" .. object.prescript
                                                    else
                                                        object.prescript = bm_trigger
                                                    end
                                                end
                                                --
                                                local before,
                                                      after,
                                                      options    = processplugins(object,result)
                                                local evenodd    = false
                                                local collect    = false
                                                local both       = false
                                                local flush      = false
                                                local outline    = force_outline
                                                local envelope   = false
                                                local postscript = object.postscript
                                                local tolerance  = options and tonumber(options.tolerance) or tolerance
                                        -- if not object.istext then
                                                if postscript == "evenodd" then
                                                    evenodd = true
                                                elseif postscript == "collect" then
                                                    collect = true
                                                elseif postscript == "flush" then
                                                    flush   = true
                                                elseif postscript == "both" then
                                                    both = true
                                                elseif postscript == "eoboth" then
                                                    evenodd = true
                                                    both    = true
                                                elseif postscript == "envelope" then
                                                    envelope = true
                                                end
                                        --  end
                                                --
                                                if flush and not savedpath then
                                                    -- forget about it
                                                elseif collect then
                                                    if not savedpath then
                                                        savedpath = { object.path or false }
                                                        savedhtap = { object.htap or false }
                                                    else
                                                        savedpath[#savedpath+1] = object.path or false
                                                        savedhtap[#savedhtap+1] = object.htap or false
                                                    end
                                                else
                                                    local objecttype = object.type -- can have changed
                                                    if envelope then
                                                        dashed, linewidth = "", 1 -- to be sure
                                                    end
                                                    if before then
                                                        -- We inject too many color and transparency directives although in practice
                                                        -- it's not that relevant. However, we can optimize this a bit because we know
                                                        -- what we can get so we just intercept these two cases:
                                                        --
                                                        -- { [1] = transparency, [2] = color }
                                                        -- { [1] = color }
                                                        --
                                                        -- It is anyway a rather ugly hack because we should keep some kind of state
                                                        -- but as we mix all kind of features that is also tricky. Maybe if I'd write
                                                        -- this from scratch ... but that doesn't pay off. Also tracking a transstate
                                                        -- is kind of fragile so maybe it should be optional.
                                                        --
                                                        if optimizecolor and after then
                                                            local r = #result
                                                            if result[r] == "0 g 0 G" and (after[1] == "0 g 0 G" or after[2] == "0 g 0 G") then
                                                                result[r] = nil
                                                                r = r - 1
                                                            end
                                                            if result[r] == "/Tr0 gs" and (after[1] == "/Tr0 gs" or after[2] == "/Tr0 gs") then
                                                                result[r] = nil
                                                            end
                                                        end
                                                        --
                                                        result = pluginactions(before,result,flushfigure)
                                                    end
                                                    local ml = object.miterlimit
                                                    if ml and ml ~= miterlimit then
                                                        miterlimit = ml
                                                        result[#result+1] = f_M(ml)
                                                    end
                                                    local lj = object.linejoin
                                                    if lj and lj ~= linejoin then
                                                        linejoin = lj
                                                        result[#result+1] = f_j(lj)
                                                    end
                                                    local lc = object.linecap
                                                    if lc and lc ~= linecap then
                                                        linecap = lc
                                                        result[#result+1] = f_J(lc)
                                                    end
                                                    if both then
                                                        if dashed ~= false then -- was just dashed test
                                                           result[#result+1] = "[] 0 d"
                                                           dashed = false
                                                        end
                                                    else
                                                        local dl = object.dash
                                                        if dl then
                                                            local d = f_d(concat(dl.dashes or {}," "),dl.offset)
                                                            if d ~= dashed then
                                                                dashed = d
                                                                result[#result+1] = d
                                                            end
                                                        elseif dashed ~= false then -- was just dashed test
                                                           result[#result+1] = "[] 0 d"
                                                           dashed = false
                                                        end
                                                    end
                                                    local path        = object.path -- newpath
                                                    local pen         = object.pen
                                                    local transformed = false
                                                    local penwidth    = 1
                                                    local length      = path and #path or 0
                                                    local last        = path and path[length]
                                                    --
                                                    -- todo: also pass length to other constructors
                                                    --
                                                    if path and type(last) == "string" then
                                                        --
                                                        -- We have a curvature 2 driven table here with a last entry being
                                                        -- the key to what is needed: { x1, y1, ... xN, yN, kind }.
                                                        --
                                                        local mesh = path[length - 1] -- To be used as a plugin or so.
                                                        local open = last == "open"
                                                        --
                                                        if pen and pen.type == "elliptical" or outline then
                                                            transformed, penwidth = pen_characteristics(object.peninfo) -- boolean, value
                                                            if penwidth ~= linewidth then
                                                                result[#result+1] = f_w(penwidth)
                                                                linewidth = penwidth
                                                            end
                                                        end
                                                        --
                                                        if last == "repeat" then
                                                            -- Here we have a curvature 3 path so we use stored curvature 2
                                                            -- one.
                                                            for i=1,repeated do
                                                                result[#result+1] = repeater[i]
                                                            end
                                                        else
                                                            local start = #result + 1
                                                            if last == "rectangle" then
                                                                flushrectangularlist(path,result)
                                                            else
                                                                flushnormallist(path,length-2,result,open)
                                                            end
                                                            repeated = 0
                                                            for i=start,#result do
                                                                repeated = repeated + 1
                                                                repeater[repeated] = result[i]
                                                            end
                                                        end
                                                        if objecttype == "fill" then
                                                            result[#result+1] = open and "f" or "h f"
                                                        elseif objecttype == "outline" then
                                                            result[#result+1] = open and "S" or "h S"
                                                        elseif objecttype == "both" then
                                                            result[#result+1] = open and "B" or "h B"
                                                        end
                                                        savedpath = nil
                                                        transformed = false
                                                    else
                                                        -- at this moment only "end_point"
                                                        local open = path and path[1].left_type and path[length].right_type
                                                        --
                                                        if pen then
                                                           if pen.type == "elliptical" or outline then
                                                                transformed, penwidth = pen_characteristics(object.peninfo) -- boolean, value
                                                                if penwidth ~= linewidth then
                                                                    result[#result+1] = f_w(penwidth)
                                                                    linewidth = penwidth
                                                                end
                                                                if objecttype == "fill" then
                                                                    objecttype = "both"
                                                                end
                                                           else -- calculated by mplib itself
                                                                objecttype = "fill"
                                                           end
                                                        end
                                                        --
                                                        if transformed then
                                                            result[#result+1] = "q"
                                                        end
                                                        if path then
                                                            if savedpath then
                                                                for i=1,#savedpath do
                                                                    local path = savedpath[i]
                                                                    local open = not path.cycle
                                                                    if transformed then
                                                                        flushconcatpath(path,result,open,tolerance,i==1)
                                                                    elseif path.rectangle then
                                                                        flushrectangularpath(path,result)
                                                                    else
                                                                        flushnormalpath(path,result,open,tolerance,false)
                                                                    end
                                                                end
                                                                savedpath = nil
                                                            end
                                                            if flush then
                                                                -- ignore this path
                                                            elseif transformed then
                                                                flushconcatpath(path,result,open,tolerance,true)
                                                            elseif path.rectangle then
                                                                flushrectangularpath(path,result)
                                                            else
                                                                flushnormalpath(path,result,open,tolerance,false)
                                                            end
                                                            if path.rectangle then
                                                                if outline or envelope then
                                                                    result[#result+1] = "S"
                                                                elseif objecttype == "fill" then
                                                                    result[#result+1] = "f"
                                                                elseif objecttype == "outline" then
                                                                    result[#result+1] = both and "B" or "S"
                                                                elseif objecttype == "both" then
                                                                    result[#result+1] = "B"
                                                                end
                                                            elseif outline or envelope then
                                                                result[#result+1] = open and "S" or "h S"
                                                            elseif objecttype == "fill" then
                                                                result[#result+1] = evenodd and "h f*" or "h f" -- f* = eo
                                                            elseif objecttype == "outline" then
                                                                if both then
                                                                    result[#result+1] = evenodd and "h B*" or "h B" -- B* = eo
                                                                else
                                                                    result[#result+1] = open and "S" or "h S"
                                                                end
                                                            elseif objecttype == "both" then
                                                                result[#result+1] = evenodd and "h B*" or "h B" -- B* = eo -- b includes closepath
                                                            end
                                                        end
                                                        if transformed then
                                                            result[#result+1] = "Q"
                                                        end
                                                        local path = object.htap
                                                        if path then
                                                            if transformed then
                                                                result[#result+1] = "q"
                                                            end
                                                            if savedhtap then
                                                                for i=1,#savedhtap do
                                                                    local path = savedhtap[i]
                                                                    local open = not path.cycle
                                                                    if transformed then
                                                                        flushconcatpath(path,result,open,tolerance,i==1)
                                                                    elseif path.rectangle then
                                                                        flushrectangularpath(path,result)
                                                                    else
                                                                        flushnormalpath(path,result,open,tolerance,false)
                                                                    end
                                                                end
                                                                savedhtap = nil
                                                                evenodd   = true
                                                            end
                                                            if transformed then
                                                                flushconcatpath(path,result,open,tolerance,true)
                                                            elseif path.rectangle then
                                                                flushrectangularpath(path,result)
                                                            else
                                                                flushnormalpath(path,result,open,tolerance,false)
                                                            end
                                                            if outline or envelope then
                                                                result[#result+1] = open and "S" or "h S"
                                                            elseif objecttype == "fill" then
                                                                result[#result+1] = evenodd and "h f*" or "h f" -- f* = eo
                                                            elseif objecttype == "outline" then
                                                                result[#result+1] = open and "S" or "h S"
                                                            elseif objecttype == "both" then
                                                                result[#result+1] = evenodd and "h B*" or "h B" -- B* = eo -- b includes closepath
                                                            end
                                                            if transformed then
                                                                result[#result+1] = "Q"
                                                            end
                                                        end
                                                    end
                                                    if after then
                                                        result = pluginactions(after,result,flushfigure)
                                                    end
                                                end
                                                if object.grouped then
                                                    -- can be qQ'd so changes can end up in groups
                                                    miterlimit, linecap, linejoin, dashed, linewidth = -1, -1, -1, "", false
                                                end
                                            elseif objecttype == "start_clip" then
                                             -- local evenodd = not object.istext and object.postscript == "evenodd"
                                                local evenodd = object.postscript == "evenodd"
                                                result[#result+1] = "q"
                                                -- maybe also rectangle check
                                                flushnormalpath(object.path,result,false,tolerance,true)
                                                result[#result+1] = evenodd and "W* n" or "W n"
                                            elseif objecttype == "stop_clip" then
                                                result[#result+1] = "Q"
                                                miterlimit, linecap, linejoin, dashed, linewidth = -1, -1, -1, "", false
                                            elseif objecttype == "start_bounds" or objecttype == "stop_bounds" then
                                                -- skip
                                            elseif objecttype == "start_group" then
                                                result[#result+1] = "% mps group: begin"
                                                if lpdf.flushgroup then
                                                    local before, after = processplugins(object,result)
                                                    local detail = object.detail
                                                    if detail or before then
                                                        result[#result+1] = "q"
                                                        result = pluginactions(before,result,flushfigure)
                                                        insert(groupstack, {
                                                            after  = after,
                                                            result = result,
                                                            detail = detail,
                                                            bbox   = toboundingbox(object.path),
                                                        })
                                                        result = { }
                                                        miterlimit, linecap, linejoin, dashed, linewidth = -1, -1, -1, "", false
                                                    else
                                                        insert(groupstack,false)
                                                    end
                                                else
                                                    insert(groupstack,false)
                                                end
                                            elseif objecttype == "stop_group" then
                                                local data = remove(groupstack)
                                                if data then
                                                    local reference = lpdf.flushgroup(concat(result,"\r"),data.bbox,data.detail)
                                                    -- crashes on after = false
                                                    result = data.result
                                                    result[#result+1] = reference
                                                    result = pluginactions(data.after,result,flushfigure)
                                                    result[#result+1] = "Q"
                                                    miterlimit, linecap, linejoin, dashed, linewidth = -1, -1, -1, "", false
                                                end
                                         -- if objecttype == "text" then
                                         --     result[#result+1] = "q"
                                         --     local ot = object.transform -- 3,4,5,6,1,2
                                         --     result[#result+1] = f_cm(ot[3],ot[4],ot[5],ot[6],ot[1],ot[2])
                                         --     flushfigure(result) -- flush accumulated literals
                                         --     result = { }
                                         --     textfigure(object.font,object.dsize,object.text,object.width,object.height,object.depth)
                                         --     result[#result+1] = "Q"
                                         -- elseif objecttype == "special" then
                                         --     if processspecial then
                                         --         processspecial(object.prescript)
                                         --     end
                                         -- else
                                                -- error
                                                result[#result+1] = "% mps group: end"
                                            end
                                        end
                                    end
                                end
                            end
                            result[#result+1] = "Q"
                            if tocomment then
                                result[#result+1] = tocomment("end")
                            end
                            flushfigure(result)
                        end
                        startfigure(properties.number,llx,lly,urx,ury,"begin",figure,plugmode)
                        if incontext then
                            context(function() processfigure() end)
                        else
                            processfigure()
                        end
                        stopfigure("end")
                    end
                    if askedfig ~= "all" then
                        break
                    end
                end
                popproperties()
            end
            resetplugins(result) -- we should move the colorinitializer here
        end
    end
end

-- tracing:

do

    local result = { }

    local flusher = {
        startfigure = function()
            result = { }
            context.startnointerference()
        end,
        flushfigure = function(literals)
            local n = #result
            for i=1,#literals do
                result[n+i] = literals[i]
            end
        end,
        stopfigure = function()
            context.stopnointerference()
        end
    }

    local specification = {
        flusher   = flusher,
     -- incontext = true,
    }

    function metapost.pdfliterals(result)
        metapost.flush(specification,result)
        return result
    end

end

function metapost.totable(result,askedfig)
    local askedfig = askedfig or 1
    local figure   = result and result.fig and result.fig[1]
    if figure then
        local results = { }
        local objects = figure:objects()
        for o=1,#objects do
            local object = objects[o]
            local result = { }
            local fields = getfields(object) -- hm, is this the whole list, if so, we can get it once
            for f=1,#fields do
                local field   = fields[f]
                result[field] = object[field]
            end
            results[o] = result
        end
        local boundingbox = figure:boundingbox()
        return {
            boundingbox = {
                llx = boundingbox[1],
                lly = boundingbox[2],
                urx = boundingbox[3],
                ury = boundingbox[4],
            },
            objects = results
        }
    else
        return nil
    end
end
