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

local format, concat, round = string.format, table.concat, math.round
local sprint = tex.sprint

colors = colors or { }

local rgbtocmyk  = colors.rgbtocmyk   or function() return 0,0,0,1 end
local cmyktorgb  = colors.cmyktorgb   or function() return 0,0,0   end
local rgbtogray  = colors.rgbtogray   or function() return 0       end
local cmyktogray = colors.cmyktogray  or function() return 0       end

metapost               = metapost or { }
metapost.specials      = metapost.specials or { }
metapost.specials.data = metapost.specials.data or { }

local data = metapost.specials.data

local colordata = { {}, {}, {}, {}, {} }

--~ (r,g,b) => cmyk             : r=123 g=   1 b=hash
--~         => spot             : r=123 g=   2 b=hash
--~         => transparent rgb  : r=123 g=   3 b=hash
--~         => transparent cmyk : r=123 g=   4 b=hash
--~         => transparent spot : r=123 g=   5 b=hash
--~         => rest             : r=123 g=n>10 b=whatever

function metapost.specials.register(str) -- only colors
    local size, content, n, class = str:match("^%%%%MetaPostSpecial: (%d+) (.*) (%d+) (%d+)$")
    if class then
        local data = { }
        for s in content:gmatch("[^ ]+") do
            data[#data+1] = s
        end
        class, n = tonumber(class), tonumber(n)
        if class == 3 or class == 4 or class == 5 then -- weird
            colordata[class][n] = data
        else
            colordata[class][tonumber(data[1])] = data
        end
    end
end

function metapost.colorhandler(cs, object, result, colorconverter)
    local cr = "0 g 0 G"
    local what = round(cs[2]*10000)
    local data = colordata[what][round(cs[3]*10000)]
    if not data then
        --
    elseif what == 1 then
        result[#result+1], cr = colorconverter({ data[2], data[3], data[4], data[5] })
    elseif what == 2 then
        ctx.registerspotcolor(data[2])
        result[#result+1] = ctx.pdfcolor(colors.model,colors.register('color',nil,'spot',data[2],data[3],data[4],data[5]))
    else
        if what == 3 then
            result[#result+1], cr = colorconverter({ data[3], data[4], data[5]})
        elseif what == 4 then
            result[#result+1], cr = colorconverter({ data[3], data[4], data[5], data[6]})
        elseif what == 5 then
            ctx.registerspotcolor(data[3])
            result[#result+1] = ctx.pdfcolor(colors.model,colors.register('color',nil,'spot',data[3],data[4],data[5],data[6]))
        end
        object.prescript = "tr"
        object.postscript = data[1] .. "," .. data[2]
    end
    object.color = nil
    return object, cr
end

function metapost.colorspec(cs)
    local what = round(cs[2]*10000)
    local data = colordata[what][round(cs[3]*10000)]
    if not data then
        return { 0 }
    elseif what == 1 then
        return { data[2], data[3], data[4], data[5] }
    elseif what == 2 then
        ctx.registerspotcolor(data[2])
        return ctx.pdfcolor(colors.model,colors.register('color',nil,'spot',data[2],data[3],data[4],data[5]))
    elseif what == 3 then
        return { data[3], data[4], data[5] }
    elseif what == 4 then
        return { data[3], data[4], data[5], data[6] }
    elseif what == 5 then
        ctx.registerspotcolor(data[3])
        return ctx.pdfcolor(colors.model,colors.register('color',nil,'spot',data[3],data[4],data[5],data[6]))
    end
end

function metapost.specials.tr(specification,object,result)
    local a, t = specification:match("^(.+),(.+)$")
    local before = a and t and function()
        result[#result+1] = format("/Tr%s gs",transparencies.register('mp',a,t))
        return object, result
    end
    local after = before and function()
        result[#result+1] = "/Tr0 gs"
        return object, result
    end
    return object, before, nil, after
end

--~ -- possible speedup: hash registered colors
--~
--~ function metapost.specials.sp(specification,object,result) -- todo: color conversion
--~     local s = object.color[1]
--~     object.color = nil
--~     local before = function()
--~         local spec = specification:split(" ")
--~         ctx.registerspotcolor(spec[1])
--~         result[#result+1] = ctx.pdfcolor(colors.model,colors.register('color',nil,'spot',spec[1],spec[2],spec[3],s))
--~         return object, result
--~     end
--~     local after = function()
--~         result[#result+1] = "0 g 0 G"
--~         return object, result
--~     end
--~     return object, before, nil, nil
--~ end

-- Unfortunately we cannot use cmyk colors natively because there is no
-- generic color allocation primitive ... it's just an rgbcolor color.. This
-- means that we cannot pass colors in either cmyk or rgb form.
--
-- def cmyk(expr c,m,y,k) =
--     1 withprescript "cc" withpostscript ddddecimal (c,m,y,k)
-- enddef ;
--
-- This is also an example of a simple plugin.

--~ function metapost.specials.cc(specification,object,result)
--~     object.color = specification:split(" ")
--~     return object, nil, nil, nil
--~ end
--~ function metapost.specials.cc(specification,object,result)
--~     local c = specification:split(" ")
--~     local o = object.color[1]
--~     c[1],c[2],c[3],c[4] = o*c[1],o*c[2],o*c[3],o*c[4]
--~     return object, nil, nil, nil
--~ end

-- thanks to taco's reading of the postscript manual:
--
-- x' = sx * x + ry * y + tx
-- y' = rx * x + sy * y + ty

function metapost.specials.fg(specification,object,result,flusher)
    local op = object.path
    local first, second, fourth  = op[1], op[2], op[4]
    local tx, ty = first.x_coord      , first.y_coord
    local sx, sy = second.x_coord - tx, fourth.y_coord - ty
    local rx, ry = second.y_coord - ty, fourth.x_coord - tx
    if sx == 0 then sx = 0.00001 end
    if sy == 0 then sy = 0.00001 end
    local before = specification and function()
        flusher.flushfigure(result)
        sprint(tex.ctxcatcodes,format("\\MPLIBfigure{%f}{%f}{%f}{%f}{%f}{%f}{%s}",sx,rx,ry,sy,tx,ty,specification))
        return object, { }
    end
    return { } , before, nil, nil -- replace { } by object for tracing
end

local nofshades = 0 -- todo: hash resources, start at 1000 in order not to clash with older

local function normalize(ca,cb)
    if #cb == 1 then
        if #ca == 4 then
            cb[1], cb[2], cb[3], cb[4] = 0, 0, 0, 1-cb[1]
        else
            cb[1], cb[2], cb[3] = cb[1], cb[1], cb[1]
        end
    elseif #cb == 3 then
        if #ca == 4 then
            cb[1], cb[2], cb[3], cb[4] = rgbtocmyk(cb[1],cb[2],cb[3])
        else
            cb[1], cb[2], cb[3] = cmyktorgb(cb[1],cb[2],cb[3],cb[4])
        end
    end
end

function metapost.specials.cs(specification,object,result,flusher) -- spot colors?
    nofshades = nofshades + 1
    flusher.flushfigure(result)
    result = { }
    local t = specification:split(" ")
    -- we need a way to move/scale
    local ca = t[4]:split(":")
    local cb = t[8]:split(":")
    if round(ca[1]*10000) == 123 then ca = metapost.colorspec(ca) end
    if round(cb[1]*10000) == 123 then cb = metapost.colorspec(cb) end
    if type(ca) == "string" then
        -- spot color, not supported, maybe at some point use the fallbacks
        sprint(tex.ctxcatcodes,format("\\MPLIBcircularshade{%s}{%s %s}{%.3f}{%.3f}{%s}{%s}{%s %s %s %s %s %s}",
            nofshades,
            t[1], t[2], 0, 1, 1, "DeviceGray",
            t[5], t[6], t[7], t[9], t[10], t[11]))
    else
        if #ca > #cb then
            normalize(ca,cb)
        elseif #ca < #cb then
            normalize(cb,ca)
        end
        local model = colors.model
        if model == "all" then
            model= (#ca == 4 and "cmyk") or (#ca == 3 and "rgb") or "gray"
        end
        if model == "rgb" then
            if #ca == 4 then
                ca[1], ca[2], ca[3] = cmyktorgb(ca[1],ca[2],ca[3],ca[4])
                cb[1], cb[2], cb[3] = cmyktorgb(cb[1],cb[2],cb[3],cb[4])
            elseif #ca == 1 then
                local a, b = 1-ca[1], 1-cb[1]
                ca[1], ca[2], ca[3] = a, a, a
                cb[1], cb[2], cb[3] = b, b, b
            end
            sprint(tex.ctxcatcodes,format("\\MPLIBcircularshade{%s}{%s %s}{%.3f %.3f %.3f}{%.3f %.3f %.3f}{%s}{%s}{%s %s %s %s %s %s}",
                nofshades,
                t[1], t[2], ca[1], ca[2], ca[3], cb[1], cb[2], cb[3], 1, "DeviceRGB",
                t[5], t[6], t[7], t[9], t[10], t[11]))
        elseif model == "cmyk" then
            if #ca == 3 then
                ca[1], ca[2], ca[3], ca[4] = rgbtocmyk(ca[1],ca[2],ca[3])
                cb[1], cb[2], cb[3], ca[4] = rgbtocmyk(cb[1],cb[2],cb[3])
            elseif #ca == 1 then
                ca[1], ca[2], ca[3], ca[4] = 0, 0, 0, ca[1]
                cb[1], cb[2], cb[3], ca[4] = 0, 0, 0, ca[1]
            end
            sprint(tex.ctxcatcodes,format("\\MPLIBcircularshade{%s}{%s %s}{%.3f %.3f %.3f %.3f}{%.3f %.3f %.3f %.3f}{%s}{%s}{%s %s %s %s %s %s}",
                nofshades,
                t[1], t[2], ca[1], ca[2], ca[3], ca[4], cb[1], cb[2], cb[3], cb[4], 1, "DeviceCMYK",
                t[5], t[6], t[7], t[9], t[10], t[11]))
        else
            if #ca == 4 then
                ca[1] = cmyktogray(ca[1],ca[2],ca[3],ca[4])
                cb[1] = cmyktogray(cb[1],cb[2],cb[3],cb[4])
            elseif #ca == 3 then
                ca[1] = rgbtogray(ca[1],ca[2],ca[3])
                cb[1] = rgbtogray(cb[1],cb[2],cb[3])
            end
            sprint(tex.ctxcatcodes,format("\\MPLIBcircularshade{%s}{%s %s}{%.3f}{%.3f}{%s}{%s}{%s %s %s %s %s %s}",
                nofshades,
                t[1], t[2], ca[1], cb[1], 1, "DeviceGray",
                t[5], t[6], t[7], t[9], t[10], t[11]))
        end
    end
    local before = function()
        result[#result+1] = "q /Pattern cs"
        return object, result
    end
    local after = function()
        result[#result+1] = format("W n /MpSh%s sh Q", nofshades)
        return object, result
    end
    object.color, object.type = nil, nil
    return object, before, nil, after
end

function metapost.specials.ls(specification,object,result,flusher)
    nofshades = nofshades + 1
    flusher.flushfigure(result)
    result = { }
    local t = specification:split(" ")
    -- we need a way to move/scale
    local ca = t[4]:split(":")
    local cb = t[7]:split(":")
    if round(ca[1]*10000) == 123 then ca = metapost.colorspec(ca) end
    if round(cb[1]*10000) == 123 then cb = metapost.colorspec(cb) end
    if type(ca) == "string" then
        -- spot color, not supported, maybe at some point use the fallbacks
        sprint(tex.ctxcatcodes,format("\\MPLIBlinearshade{%s}{%s %s}{%.3f}{%.3f}{%s}{%s}{%s %s %s %s}",
            nofshades,
            t[1], t[2], 0, 1, 1, "DeviceGray",
            t[5], t[6], t[8], t[9]))
    else
        if #ca > #cb then
            normalize(ca,cb)
        elseif #ca < #cb then
            normalize(cb,ca)
        end
        local model = colors.model
        if model == "all" then
            model= (#ca == 4 and "cmyk") or (#ca == 3 and "rgb") or "gray"
        end
        if model == "rgb" then
            if #ca == 4 then
                ca[1], ca[2], ca[3] = cmyktorgb(ca[1],ca[2],ca[3],ca[4])
                cb[1], cb[2], cb[3] = cmyktorgb(cb[1],cb[2],cb[3],cb[4])
            elseif #ca == 1 then
                local a, b = 1-ca[1], 1-cb[1]
                ca[1], ca[2], ca[3] = a, a, a
                cb[1], cb[2], cb[3] = b, b, b
            end
            sprint(tex.ctxcatcodes,format("\\MPLIBlinearshade{%s}{%s %s}{%.3f %.3f %.3f}{%.3f %.3f %.3f}{%s}{%s}{%s %s %s %s}",
                nofshades,
                t[1], t[2], ca[1], ca[2], ca[3], cb[1], cb[2], cb[3], 1, "DeviceRGB",
                t[5], t[6], t[8], t[9]))
        elseif model == "cmyk" then
            if #ca == 3 then
                ca[1], ca[2], ca[3], ca[4] = rgbtocmyk(ca[1],ca[2],ca[3])
                cb[1], cb[2], cb[3], ca[4] = rgbtocmyk(cb[1],cb[2],cb[3])
            elseif #ca == 1 then
                ca[1], ca[2], ca[3], ca[4] = 0, 0, 0, ca[1]
                cb[1], cb[2], cb[3], ca[4] = 0, 0, 0, ca[1]
            end
            sprint(tex.ctxcatcodes,format("\\MPLIBlinearshade{%s}{%s %s}{%.3f %.3f %.3f %.3f}{%.3f %.3f %.3f %.3f}{%s}{%s}{%s %s %s %s}",
                nofshades,
                t[1], t[2], ca[1], ca[2], ca[3], ca[4], cb[1], cb[2], cb[3], cb[4], 1, "DeviceCMYK",
                t[5], t[6], t[8], t[9]))
        else
            if #ca == 4 then
                ca[1] = cmyktogray(ca[1],ca[2],ca[3],ca[4])
                cb[1] = cmyktogray(cb[1],cb[2],cb[3],cb[4])
            elseif #ca == 3 then
                ca[1] = rgbtogray(ca[1],ca[2],ca[3])
                cb[1] = rgbtogray(cb[1],cb[2],cb[3])
            end
            sprint(tex.ctxcatcodes,format("\\MPLIBlinearshade{%s}{%s %s}{%.3f}{%.3f}{%s}{%s}{%s %s %s %s}",
                nofshades,
                t[1], t[2], ca[1], cb[1], 1, "DeviceGray",
                t[5], t[6], t[8], t[9]))
        end
    end
    local before = function()
        result[#result+1] = "q /Pattern cs"
        return object, result
    end
    local after = function()
        result[#result+1] = format("W n /MpSh%s sh Q", nofshades)
        return object, result
    end
    object.color, object.type = nil, nil
    return object, before, nil, after
end

-- no need for a before here

local current_format, current_graphic

--~ metapost.first_box, metapost.last_box = 1000, 1100

metapost.textext_current = metapost.first_box
metapost.trace_texttexts = false
metapost.multipass       = false

function metapost.specials.tf(specification,object)
--~ print("setting", metapost.textext_current)
    local n, str = specification:match("^(%d+):(.+)$")
    if metapost.textext_current < metapost.last_box then
        metapost.textext_current = metapost.first_box + n - 1
    end
    if metapost.trace_texttexts then
        print("metapost", format("first pass: order %s, box %s",n,metapost.textext_current))
    end
    sprint(tex.ctxcatcodes,format("\\MPLIBsettext{%s}{%s}",metapost.textext_current,str))
    metapost.multipass = true
    return { }, nil, nil, nil
end

function metapost.specials.ts(specification,object,result,flusher)
    -- print("getting", metapost.textext_current)
    local n, str = specification:match("^(%d+):(.+)$")
    if metapost.trace_texttexts then
        print("metapost", format("second pass: order %s, box %s",n,metapost.textext_current))
    end
    local op = object.path
    local first, second, fourth  = op[1], op[2], op[4]
    local tx, ty = first.x_coord      , first.y_coord
    local sx, sy = second.x_coord - tx, fourth.y_coord - ty
    local rx, ry = second.y_coord - ty, fourth.x_coord - tx
    if sx == 0 then sx = 0.00001 end
    if sy == 0 then sy = 0.00001 end
    local before = function() -- no need for function
    --~ flusher.flushfigure(result)
    --~ sprint(tex.ctxcatcodes,format("\\MPLIBgettext{%f}{%f}{%f}{%f}{%f}{%f}{%s}",sx,rx,ry,sy,tx,ty,metapost.textext_current))
    --~ result = { }
        result[#result+1] = format("q %f %f %f %f %f %f cm", sx,rx,ry,sy,tx,ty)
        flusher.flushfigure(result)
        if metapost.textext_current < metapost.last_box then
            metapost.textext_current = metapost.first_box + n - 1
        end
        local b = metapost.textext_current
        sprint(tex.ctxcatcodes,format("\\MPLIBgettextscaled{%s}{%s}{%s}",b, metapost.sxsy(tex.wd[b],tex.ht[b],tex.dp[b])))
        result = { "Q" }
        return object, result
    end
    return { }, before, nil, nil -- replace { } by object for tracing
end

function metapost.colorconverter()
    -- it no longer pays off to distinguish between outline and fill
    --  (we now have both too, e.g. in arrows)
    local model = colors.model
    if model == "all" then
        return function(cr)
            local n = #cr
            if n == 1 then
                local s = cr[1]
                return format("%.3f g %.3f G",s,s), "0 g 0 G"
            elseif n == 4 then
                local c, m, y, k = cr[1], cr[2], cr[3], cr[4]
                return format("%.3f %.3f %.3f %.3f k %.3f %.3f %.3f %.3f K",c,m,y,k,c,m,y,k), "0 g 0 G"
            else
                local r, g, b = cr[1], cr[2], cr[3]
                return format("%.3f %.3f %.3f rg %.3f %.3f %.3f RG",r,g,b,r,g,b), "0 g 0 G"
            end
        end
    elseif model == "rgb" then
        return function(cr)
            local n = #cr
            if n == 1 then
                local s = cr[1]
                return format("%.3f g %.3f G",s,s), "0 g 0 G"
            end
            local r, g, b
            if n == 4 then
                r, g, b = cmyktorgb(cr[1],cr[2],cr[3],cr[4])
            else
                r, g, b = cr[1],cr[2],cr[3]
            end
            return format("%.3f %.3f %.3f rg %.3f %.3f %.3f RG",r,g,b,r,g,b), "0 g 0 G"
        end
    elseif model == "cmyk" then
        return function(cr)
            local n = #cr
            if n == 1 then
                local s = cr[1]
                return format("%.3f g %.3f G",s,s), "0 g 0 G"
            end
            local c, m, y, k
            if n == 4 then
                c, m, y, k = cr[1], cr[2], cr[3], cr[4]
            else
                c, m, y, k = rgbtocmyk(cr[1],cr[2],cr[3])
            end
            return format("%.3f %.3f %.3f %.3f k %.3f %.3f %.3f %.3f K",c,m,y,k,c,m,y,k), "0 g 0 G"
        end
    else
        return function(cr)
            local s
            local n = #cr
            if n == 4 then
                s = cmyktogray(cr[1],cr[2],cr[3],cr[4])
            elseif n == 3 then
                s = rgbtogray(cr[1],cr[2],cr[3])
            else
                s = cr[1]
            end
            return format("%.3f g %.3f G",s,s), "0 g 0 G"
        end
    end
end

--~ local cmyk_fill    = "%.3f %.3f %.3f %.3f k"
--~ local rgb_fill     = "%.3f %.3f %.3f rg"
--~ local gray_fill    = "%.3f g"
--~ local reset_fill   = "0 g"

--~ local cmyk_stroke  = "%.3f %.3f %.3f %.3f K"
--~ local rgb_stroke   = "%.3f %.3f %.3f RG"
--~ local gray_stroke  = "%.3f G"
--~ local reset_stroke = "0 G"

metapost.reducetogray = true

function metapost.colorconverter() -- rather generic pdf, so use this elsewhere too
    -- it no longer pays off to distinguish between outline and fill
    --  (we now have both too, e.g. in arrows)
    local model = colors.model
    local reduce = metapost.reducetogray
    if model == "all" then
        return function(cr)
            local n = #cr
            if reduce then
                if n == 1 then
                    local s = cr[1]
                    return format("%.3f g %.3f G",s,s), "0 g 0 G"
                elseif n == 3 then
                    local r, g, b = cr[1], cr[2], cr[3]
                    if r == g and g == b then
                        return format("%.3f g %.3f G",r,r), "0 g 0 G"
                    else
                        return format("%.3f %.3f %.3f rg %.3f %.3f %.3f RG",r,g,b,r,g,b), "0 g 0 G"
                    end
                else
                    local c, m, y, k = cr[1], cr[2], cr[3], cr[4]
                    if c == m and m == y and y == 0 then
                        k = 1 - k
                        return format("%.3f g %.3f G",k,k), "0 g 0 G"
                    else
                        return format("%.3f %.3f %.3f %.3f k %.3f %.3f %.3f %.3f K",c,m,y,k,c,m,y,k), "0 g 0 G"
                    end
                end
            elseif n == 1 then
                local s = cr[1]
                return format("%.3f g %.3f G",s,s), "0 g 0 G"
            elseif n == 3 then
                local r, g, b = cr[1], cr[2], cr[3]
                return format("%.3f %.3f %.3f rg %.3f %.3f %.3f RG",r,g,b,r,g,b), "0 g 0 G"
            else
                local c, m, y, k = cr[1], cr[2], cr[3], cr[4]
                return format("%.3f %.3f %.3f %.3f k %.3f %.3f %.3f %.3f K",c,m,y,k,c,m,y,k), "0 g 0 G"
            end
        end
    elseif model == "rgb" then
        return function(cr)
            local n = #cr
            if reduce then
                if n == 1 then
                    local s = cr[1]
                    return format("%.3f g %.3f G",s,s), "0 g 0 G"
                elseif n == 3 then
                    local r, g, b = cr[1], cr[2], cr[3]
                    if r == g and g == b then
                        return format("%.3f g %.3f G",r,r), "0 g 0 G"
                    else
                        return format("%.3f %.3f %.3f rg %.3f %.3f %.3f RG",r,g,b,r,g,b), "0 g 0 G"
                    end
                else
                    local c, m, y, k = cr[1], cr[2], cr[3], cr[4]
                    if c == m and m == y and y == 0 then
                        k = 1 - k
                        return format("%.3f g %.3f G",k,k), "0 g 0 G"
                    else
                        local r, g, b = cmyktorgb(c,m,y,k)
                        return format("%.3f %.3f %.3f rg %.3f %.3f %.3f RG",r,g,b,r,g,b), "0 g 0 G"
                    end
                end
            elseif n == 1 then
                local s = cr[1]
                return format("%.3f g %.3f G",s,s), "0 g 0 G"
            else
                local r, g, b
                if n == 3 then
                    r, g, b = cmyktorgb(cr[1],cr[2],cr[3],cr[4])
                else
                    r, g, b = cr[1], cr[2], cr[3]
                end
                return format("%.3f %.3f %.3f rg %.3f %.3f %.3f RG",r,g,b,r,g,b), "0 g 0 G"
            end
        end
    elseif model == "cmyk" then
        return function(cr)
            local n = #cr
            if reduce then
                if n == 1 then
                    local s = cr[1]
                    return format("%.3f g %.3f G",s,s), "0 g 0 G"
                elseif n == 3 then
                    local r, g, b = cr[1], cr[2], cr[3]
                    if r == g and g == b then
                        return format("%.3f g %.3f G",r,r), "0 g 0 G"
                    else
                        local c, m, y, k = rgbtocmyk(r,g,b)
                        return format("%.3f %.3f %.3f %.3f k %.3f %.3f %.3f %.3f K",c,m,y,k,c,m,y,k), "0 g 0 G"
                    end
                else
                    local c, m, y, k = cr[1], cr[2], cr[3], cr[4]
                    if c == m and m == y and y == 0 then
                        k = 1 - k
                        return format("%.3f g %.3f G",k,k), "0 g 0 G"
                    else
                        return format("%.3f %.3f %.3f %.3f k %.3f %.3f %.3f %.3f K",c,m,y,k,c,m,y,k), "0 g 0 G"
                    end
                end
            elseif n == 1 then
                local s = cr[1]
                return format("%.3f g %.3f G",s,s), "0 g 0 G"
            else
                local c, m, y, k
                if n == 3 then
                    c, m, y, k = rgbtocmyk(cr[1],cr[2],cr[3])
                else
                    c, m, y, k = cr[1], cr[2], cr[3], cr[4]
                end
                return format("%.3f %.3f %.3f %.3f k %.3f %.3f %.3f %.3f K",c,m,y,k,c,m,y,k), "0 g 0 G"
            end
        end
    else
        return function(cr)
            local n, s = #cr, 0
            if n == 4 then
                s = cmyktogray(cr[1],cr[2],cr[3],cr[4])
            elseif n == 3 then
                s = rgbtogray(cr[1],cr[2],cr[3])
            else
                s = cr[1]
            end
            return format("%.3f g %.3f G",s,s), "0 g 0 G"
        end
    end
end

do

    local P, S, V, Cs = lpeg.P, lpeg.S, lpeg.V, lpeg.Cs

    local btex    = P("btex")
    local etex    = P(" etex")
    local vtex    = P("verbatimtex")
    local ttex    = P("textext")
    local gtex    = P("graphictext")
    local spacing = S(" \n\r\t\v")^0
    local dquote  = P('"')

    local found = false

    local function convert(str)
        found = true
        return "textext(\"" .. str .. "\")"
    end
    local function ditto(str)
        return "\" & ditto & \""
    end
    local function register()
        found = true
    end

    local parser = P {
        [1] = Cs((V(2)/register + V(3)/convert + 1)^0),
        [2] = ttex + gtex,
        [3] = (btex + vtex) * spacing * Cs((dquote/ditto + (1 - etex))^0) * etex,
    }

    -- currently a a one-liner produces less code

    local parser = Cs(((ttex + gtex)/register + ((btex + vtex) * spacing * Cs((dquote/ditto + (1 - etex))^0) * etex)/convert + 1)^0)

    function metapost.check_texts(str)
        found = false
        return parser:match(str), found
    end

end

local factor = 65536*(7200/7227)

function metapost.edefsxsy(wd,ht,dp) -- helper for text
    commands.edef("sx",(wd ~= 0 and 1/( wd    /(factor))) or 0)
    commands.edef("sy",(wd ~= 0 and 1/((ht+dp)/(factor))) or 0)
end

function metapost.sxsy(wd,ht,dp) -- helper for text
    return (wd ~= 0 and 1/(wd/(factor))) or 0, (wd ~= 0 and 1/((ht+dp)/(factor))) or 0
end

function metapost.text_texts_data()
    local t, n = { }, 0
    for i = metapost.first_box, metapost.last_box do
        n = n + 1
        if metapost.trace_texttexts then
            print("metapost", format("passed data: order %s, box %s",n,i))
        end
        if tex.box[i] then
            t[#t+1] = format("_tt_w_[%i]:=%f;_tt_h_[%i]:=%f;_tt_d_[%i]:=%f;", n,tex.wd[i]/factor, n,tex.ht[i]/factor, n,tex.dp[i]/factor)
        else
            break
        end
    end
    return t
end

metapost.intermediate         = metapost.intermediate         or {}
metapost.intermediate.actions = metapost.intermediate.actions or {}
metapost.intermediate.needed  = false

--~ function metapost.graphic_base_pass(mpsformat,str,preamble)
--~     local prepared, done = metapost.check_texts(str)
--~     metapost.textext_current = metapost.first_box
--~     metapost.intermediate.needed  = false
--~     if done then
--~         current_format, current_graphic = mpsformat, prepared
--~         metapost.process(mpsformat, {
--~             preamble or "",
--~             "beginfig(1); ",
--~             "_trial_run_ := true ;",
--~             prepared,
--~             "endfig ;"
--~         }, true ) -- true means: trialrun
--~         if metapost.intermediate.needed then
--~             for _, action in pairs(metapost.intermediate.actions) do
--~                 action()
--~             end
--~         end
--~         sprint(tex.ctxcatcodes,"\\ctxlua{metapost.graphic_extra_pass()}")
--~     else
--~         metapost.process(mpsformat, {
--~             preamble or "",
--~             "beginfig(1); ",
--~             "_trial_run_ := false ;",
--~             "resettextexts;",
--~             str,
--~             "endfig ;"
--~         } )
--~     end
--~ end

metapost.method = 1 -- 1:dumb 2:clever

function metapost.graphic_base_pass(mpsformat,str,preamble)
    local done_1, done_2
    str, done_1 = metapost.check_texts(str)
    if preamble then
        preamble, done_2 = metapost.check_texts(preamble)
    else
        preamble, done_2 = "", false
    end
    metapost.textext_current = metapost.first_box
    metapost.intermediate.needed  = false
    metapost.multipass = false -- no needed here
    current_format, current_graphic = mpsformat, str
    if metapost.method == 1 or (metapost.method == 2 and (done_1 or done_2)) then
        local flushed = metapost.process(mpsformat, {
            preamble,
            "beginfig(1); ",
            "_trial_run_ := true ;",
--~             "resettextexts;",
            str,
            "endfig ;"
        }, true, nil, true ) -- true means: trialrun, true means: avoid extra run if no multipass
        if metapost.intermediate.needed then
            for _, action in pairs(metapost.intermediate.actions) do
                action()
            end
        end
        if not flushed then
            sprint(tex.ctxcatcodes,"\\ctxlua{metapost.graphic_extra_pass()}")
        end
    else
        metapost.process(mpsformat, {
            preamble or "",
            "beginfig(1); ",
            "_trial_run_ := false ;",
--~             "resettextexts;",
            str,
            "endfig ;"
        } )
    end
end

function metapost.graphic_extra_pass()
    metapost.textext_current = metapost.first_box
    metapost.process(current_format, {
        "beginfig(0); ",
        "_trial_run_ := false ;",
--~         "resettextexts;",
        concat(metapost.text_texts_data()," ;\n"),
        current_graphic,
        "endfig ;"
    })
end

function metapost.getclippath(data)
    local mpx = metapost.format("metafun")
    if mpx and data then
        input.starttiming(metapost)
        input.starttiming(metapost.exectime)
        local result = mpx:execute(format("beginfig(1);%s;endfig;",data))
        input.stoptiming(metapost.exectime)
        if result.status > 0 then
            print("error", result.status, result.error or result.term or result.log)
            result = ""
        else
            result = metapost.filterclippath(result)
        end
        input.stoptiming(metapost)
        sprint(result)
    end
end

metapost.tex = metapost.tex or { }

do -- only used in graphictexts

    local environments = { }

    function metapost.tex.set(str)
        environments[#environments+1] = str
    end
    function metapost.tex.reset()
        environments = { }
    end
    function metapost.tex.get()
        return concat(environments,"\n")
    end

end

do -- not that beautiful but ok, we could save a md5 hash in the tui file !

    local graphics = { }
    local start    = [[\starttext]]
    local preamble = [[\long\def\MPLIBgraphictext#1{\startTEXpage[scale=10000]#1\stopTEXpage}]]
    local stop     = [[\stoptext]]

    function metapost.specials.gt(specification,object) -- number, so that we can reorder
        graphics[#graphics+1] = format("\\MPLIBgraphictext{%s}",specification)
        metapost.intermediate.needed = true
        metapost.multipass = true
        return { }, nil, nil, nil
    end

    function metapost.intermediate.actions.makempy()
        if #graphics > 0 then
            local mpofile = tex.jobname .. "-mp"
            local mpyfile = file.replacesuffix(mpofile,"mpy")
            local pdffile = file.replacesuffix(mpofile,"pdf")
            local texfile = file.replacesuffix(mpofile,"tex")
            io.savedata(texfile, { start, preamble, metapost.tex.get(), concat(graphics,"\n"), stop }, "\n")
            os.execute(format("context --once %s", texfile))
            if io.exists(pdffile) then
                os.execute(format("pstoedit -ssp -dt -f mpost %s %s", pdffile, mpyfile))
                local result = { }
                if io.exists(mpyfile) then
                    local data = io.loaddata(mpyfile)
                    for figure in data:gmatch("beginfig(.-)endfig") do
                        result[#result+1] = format("begingraphictextfig%sendgraphictextfig ;\n", figure)
                    end
                    io.savedata(mpyfile,concat(result,""))
                end
            end
            graphics = { }
        end
    end

end