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

-- text: fill white also

-- todo: svg stripper
-- todo: check clip: what if larger than bbox
-- todo: when opacity is 1 don't flush it

-- Just a few notes:
--
-- There is no real need to boost performance here .. we can always make a fast
-- variant when really needed. I will also do some of the todo's when I run into
-- proper fonts. I need to optimize this a bit but will do that once I'm satisfied
-- with the outcome and don't need more hooks and plugs. At some point I will
-- optimize the MetaPost part because now we probably have more image wrapping
-- than needed.
--
-- As usual with these standards, things like a path can be very compact while the
-- rest is very verbose which defeats the point. This is a first attempt. There will
-- be a converter to MP as well as directly to PDF. This module was made for one of
-- the dangerous curves talks at the 2019 CTX meeting. I will do the font when I
-- need it (not that hard).
--
-- The fact that in the more recent versions of SVG the older text related elements
-- are depricated and not even supposed to be supported, combined with the fact that
-- the text element assumes css styling, demonstrates that there is not so much as a
-- standard. It basically means that whatever technology dominates at some point
-- (probably combined with some libraries that at that point exist) determine what
-- is standard. Anyway, it probably also means that these formats are not that
-- suitable for long term archival purposes. So don't take the next implementation
-- too serious. So in the end we now have (1) attributes for properties (which is
-- nice and clean and what attributes are for, (2) a style attribute that needs to
-- be parsed, (3) classes that map to styles and (4) element related styles, plus a
-- kind of inheritance (given the limited number of elements sticking to only <g> as
-- wrapper would have made much sense. Anyway, we need to deal with it. With all
-- these style things going on, one can wonder where it will end. Basically svg
-- became just a html element that way and less clean too. The same is true for
-- tspan, which means that text itself is nested xml.
--
-- We can do a direct conversion to PDF but then we also loose the abstraction which
-- in the future will be used, and for fonts we need to spawn out to TeX anyway, so
-- the little overhead of calling MetaPost is okay I guess. Also, we want to
-- overload labels, share fonts with the main document, etc. and are not aiming at a
-- general purpose SVG converter. For going to PDF one can just use InkScape.
--
-- Written with Anne Clark on speakers as distraction.
--
-- Todo when I run into an example (but ony when needed and reasonable):
--
--   var(color,color)
--   --color<decimal>
--   currentColor : when i run into an example
--   a bit more shading
--   clip = [ auto | rect(llx,lly,urx,ury) ] (in svg)
--   xlink url ... whatever
--   masks
--   opacity per group (i need to add that to metafun first, inefficient pdf but
--   maybe filldraw can help here)
--
-- Maybe in metafun:
--
--   penciled    n     -> withpen pencircle scaled n
--   applied     (...) -> transformed bymatrix (...)
--   withopacity n     -> withtransparency (1,n)

-- When testing mbo files:
--
--   empty paths
--   missing control points
--   funny fontnames like abcdefverdana etc
--   paths representing glyphs but also with style specs
--   all kind of attributes
--   very weird and inefficient shading

-- One can run into pretty crazy images, like lines that are fills being clipped
-- to some width. That's the danger of hiding yourself behind an interface I guess.
--
-- One would expect official examples to sort of follow the structure guideline,
-- like putting gradient definitions in a "defs" element but forget about it ...
-- structure seems not to be important (one can even wonder why "defs" is there at
-- all). In the end all these systems (tex macro packages included) end up as a
-- mess simply because conceptually wrong input gets accepted as normal. A side
-- effect is that one starts to get a disliking. Anyway, at sime point I can just
-- simplify some code because ugliness is part of the game.

-- What a mess: opacity=0.8 and fill-opacity=1 on the same element ...

-- the fact that everything glyph is gone makes it less useful because now one is
-- dependent of some text renderer which then also removes some control because one
-- has to fight and/or abuse features to get a specific shape ... the same is true
-- for browsers ... it makes that svg is not really an output format any longer so
-- less useful in the end (so for instance to get a specific glyph one needs to find
-- a feature that provides it .. likely per glyph (om the other hand creepy shading
-- and opacity has to be supported that can easier be done otherwise) .. and what
-- about optimizeSpeed, optimizeLegibility, geometricPrecision ... it basically makes
-- using labels impossible because no one knows how it will align .. so best convert
-- text to curves!

local rawget, rawset, type, tonumber, tostring, next, setmetatable = rawget, rawset, type, tonumber, tostring, next, setmetatable

local P, S, R, C, Ct, Cs, Cc, Cp, Cg, Cf, Carg = lpeg.P, lpeg.S, lpeg.R, lpeg.C, lpeg.Ct, lpeg.Cs, lpeg.Cc, lpeg.Cp, lpeg.Cg, lpeg.Cf, lpeg.Carg

local lpegmatch, lpegpatterns = lpeg.match, lpeg.patterns
local sqrt, abs = math.sqrt, math.abs
local concat, setmetatableindex, sortedhash = table.concat, table.setmetatableindex, table.sortedhash
local gmatch, gsub, find, match = string.gmatch, string.gsub, string.find, string.match
local formatters, fullstrip = string.formatters, string.fullstrip
local utfsplit, utfbyte = utf.split, utf.byte
local isfile, isdir, mkdir, modification = lfs.isfile, lfs.isdir, lfs.mkdir, lfs.modification

local xmlconvert, xmlcollected, xmlcount, xmlfirst, xmlroot, xmlall = xml.convert, xml.collected, xml.count, xml.first, xml.root, xml.all
local xmltext, xmltextonly = xml.text, xml.textonly
local css = xml.css or { } -- testing

local function xmlinheritattributes(c,pa)
    if not c.special then
        local at = c.at
        local dt = c.dt
        if at and dt then
            if pa then
                setmetatableindex(at,pa)
            end
            for i=1,#dt do
                local dti = dt[i]
                if type(dti) == "table" then
                    xmlinheritattributes(dti,at)
                end
            end
        end
    end
end

xml.inheritattributes = xmlinheritattributes

-- Maybe some day helpers will move to the metapost.svg namespace!

metapost       = metapost or { }
local metapost = metapost
local context  = context

local report       = logs.reporter("metapost","svg")

local trace        = false  trackers.register("metapost.svg",        function(v) trace        = v end)
local trace_text   = false  trackers.register("metapost.svg.text",   function(v) trace_text   = v end)
local trace_path   = false  trackers.register("metapost.svg.path",   function(v) trace_path   = v end)
local trace_result = false  trackers.register("metapost.svg.result", function(v) trace_result = v end)
local trace_colors = false  trackers.register("metapost.svg.colors", function(v) trace_colors = v end)
local trace_fonts  = false  trackers.register("metapost.svg.fonts",  function(v) trace_fonts  = v end)

-- This is just an experiment. Todo: reset hash etc. Also implement an option handler.

local s_draw_image_start <const> = "draw image ("
local s_draw_image_stop  <const> = ") ;"

local ignoredopacity = 1

local svghash = false  do

    local svglast = 0
    local svglist = false

    local function checkhash(t,k)
        local n = svglast + 1
        svglast = n
        svglist[n] = k
        t[k] = n
        return n
    end

    function metapost.startsvghashing()
        svglast = 0
        svglist = { }
        svghash = setmetatableindex(checkhash)
    end

    function metapost.stopsvghashing()
        svglast = 0
        svglist = false
        svghash = false
    end

    interfaces.implement {
        name      = "svghashed",
        arguments = "integer",
        actions   = function(n)
            local t = svglist and svglist[n]
            if t then
                context(t)
            end
        end
    }

end

-- We have quite some closures because otherwise we run into the local variable
-- limitations. It doesn't always look pretty now, sorry. I'll clean up this mess
-- some day (the usual nth iteration of code).
--
-- Most of the conversion is rather trivial code till I ran into a file with arcs. A
-- bit of searching lead to the a2c javascript function but it has some puzzling
-- thingies (like sin and cos definitions that look like leftovers and possible
-- division by zero). Anyway, we can if needed optimize it a bit more. Here does it
-- come from:

-- http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
-- https://github.com/adobe-webplatform/Snap.svg/blob/b242f49e6798ac297a3dad0dfb03c0893e394464/src/path.js

local a2c  do

    local pi, sin, cos, tan, asin, abs = math.pi, math.sin, math.cos, math.tan, math.asin, math.abs

    local d120 = (pi * 120) / 180
    local pi2  = 2 * pi

    a2c = function(x1, y1, rx, ry, angle, large, sweep, x2, y2, f1, f2, cx, cy)

        if (rx == 0 or ry == 0) or (x1 == x2 and y1 == y2) then
            return { x1, y1, x2, y2, x2, y2 }
        end

        local recursive = f1
        local rad       = pi / 180 * angle
        local res       = nil
        local cosrad    = cos(-rad) -- local cosrad = cosd(angle)
        local sinrad    = sin(-rad) -- local sinrad = sind(angle)

        if not recursive then

            x1, y1 = x1 * cosrad - y1 * sinrad, x1 * sinrad + y1 * cosrad
            x2, y2 = x2 * cosrad - y2 * sinrad, x2 * sinrad + y2 * cosrad

            local x  = (x1 - x2) / 2
            local y  = (y1 - y2) / 2
            local xx = x * x
            local yy = y * y
            local h  = xx / (rx * rx) + yy / (ry * ry)

            if h > 1 then
                h  = sqrt(h)
                rx = h * rx
                ry = h * ry
            end

            local rx2   = rx * rx
            local ry2   = ry * ry
            local ry2xx = ry2 * xx
            local rx2yy = rx2 * yy
            local total = rx2yy + ry2xx -- otherwise overflow

            local k     = total == 0 and 0 or sqrt(abs((rx2 * ry2 - rx2yy - ry2xx) / total))

            if large == sweep then
                k = -k
            end

            cx = k *  rx * y / ry + (x1 + x2) / 2
            cy = k * -ry * x / rx + (y1 + y2) / 2

            f1 = (y1 - cy) / ry -- otherwise crash on a tiny eps
            f2 = (y2 - cy) / ry -- otherwise crash on a tiny eps

            f1 = asin((f1 < -1.0 and -1.0) or (f1 > 1.0 and 1.0) or f1)
            f2 = asin((f2 < -1.0 and -1.0) or (f2 > 1.0 and 1.0) or f2)

            if x1 < cx then f1 = pi  - f1 end
            if x2 < cx then f2 = pi  - f2 end

            if f1 < 0  then f1 = pi2 + f1 end
            if f2 < 0  then f2 = pi2 + f2 end

            if sweep ~= 0 and f1 > f2 then f1 = f1 - pi2 end
            if sweep == 0 and f2 > f1 then f2 = f2 - pi2 end

        end

        if abs(f2 - f1) > d120 then
            local f2old = f2
            local x2old = x2
            local y2old = y2
            f2 = f1 + d120 * ((sweep ~= 0 and f2 > f1) and 1 or -1)
            x2 = cx + rx * cos(f2)
            y2 = cy + ry * sin(f2)
            res = a2c(x2, y2, rx, ry, angle, 0, sweep, x2old, y2old, f2, f2old, cx, cy)
        end

        local c1 = cos(f1)
        local s1 = sin(f1)
        local c2 = cos(f2)
        local s2 = sin(f2)

        local t  = tan((f2 - f1) / 4)
        local hx = 4 * rx * t / 3
        local hy = 4 * ry * t / 3

        local r = { x1 - hx * s1, y1 + hy * c1, x2 + hx * s2, y2 - hy * c2, x2, y2, unpack(res or { }) }

        if not recursive then -- we can also check for sin/cos being 0/1
            cosrad = cos(rad)
            sinrad = sin(rad)
         -- cosrad = cosd(angle)
         -- sinrad = sind(angle)
            for i0=1,#r,2 do
                local i1 = i0 + 1
                local x  = r[i0]
                local y  = r[i1]
                r[i0] = x * cosrad - y * sinrad
                r[i1] = x * sinrad + y * cosrad
            end
        end

        return r
    end

end

-- We share some patterns.

local p_digit    = lpegpatterns.digit
local p_hexdigit = lpegpatterns.hexdigit
local p_space    = lpegpatterns.whitespace

local factors = {
    ["pt"] =  1.25,
    ["mm"] =  3.543307,
    ["cm"] = 35.43307,
    ["px"] =  1,
    ["pc"] = 15,
    ["in"] = 90,
    ["em"] = 12 * 1.25,
    ["ex"] =  8 * 1.25,
    ["%"]  =  0.1,
    ["bp"] =  1,
}

metapost.svgfactors = factors

local percentage_r = 1/100
local percentage_x = percentage_r
local percentage_y = percentage_r

local asnumber, asnumber_p, asnumber_r, asnumber_x, asnumber_y, asnumber_vx, asnumber_vy
local asnumber_vx_t, asnumber_vy_t
local p_number, p_separator, p_optseparator, p_numbers, p_fournumbers, p_path
local p_number_n, p_number_p, p_number_u, p_number_x, p_number_vx, p_number_y, p_number_vy, p_number_r

do

    -- incredible: we can find .123.456 => 0.123 0.456 ...

    local p_command_x  = C(S("Hh"))
    local p_command_y  = C(S("Vv"))
    local p_command_xy = C(S("CcLlMmQqSsTt"))
    local p_command_a  = C(S("Aa"))
    local p_command    = C(S("Zz"))

    p_optseparator = S("\t\n\r ,")^0
    p_separator    = S("\t\n\r ,")^1
    p_number       = (S("+-")^0 * (p_digit^0 * P(".") * p_digit^1 + p_digit^1 * P(".") + p_digit^1))
                   * (P("e") * S("+-")^0 * p_digit^1)^-1

    local function convert   (n)   n =   tonumber(n)                                                                           return n     end
    local function convert_p (n,u) n =   tonumber(n) if u == true then return n / 100                                     else return n end end
    local function convert_r (n,u) n =   tonumber(n) if u == true then return percentage_r * n elseif u then return u * n else return n end end
    local function convert_x (n,u) n =   tonumber(n) if u == true then return percentage_x * n elseif u then return u * n else return n end end
    local function convert_y (n,u) n =   tonumber(n) if u == true then return percentage_y * n elseif u then return u * n else return n end end
    local function convert_vx(n,u) n =   tonumber(n) if u == true then return percentage_x * n elseif u then return u * n else return n end end
    local function convert_vy(n,u) n = - tonumber(n) if u == true then return percentage_y * n elseif u then return u * n else return n end end

    local p_unit      = (P("p") * S("txc") + P("e") * S("xm") + S("mc") * P("m") + P("in")) / factors
    local p_percent   = P("%") * Cc(true)

    local c_number_n  = C(p_number)
    local c_number_u  = C(p_number) * (p_percent + p_unit)^-1

    p_number_n  = c_number_n / convert
    p_number_u  = c_number_u / convert
    p_number_x  = c_number_u / convert_x
    p_number_vx = c_number_u / convert_vx
    p_number_y  = c_number_u / convert_y
    p_number_vy = c_number_u / convert_vy
    p_number_r  = c_number_u / convert_r
    p_number_p  = c_number_u / convert_p

    asnumber    = function(s) return s and lpegmatch(p_number,   s) or 0 end
    asnumber_r  = function(s) return s and lpegmatch(p_number_r, s) or 0 end
    asnumber_p  = function(s) return s and lpegmatch(p_number_p, s) or 0 end
    asnumber_x  = function(s) return s and lpegmatch(p_number_x, s) or 0 end
    asnumber_y  = function(s) return s and lpegmatch(p_number_y, s) or 0 end
    asnumber_vx = function(s) return s and lpegmatch(p_number_vx,s) or 0 end
    asnumber_vy = function(s) return s and lpegmatch(p_number_vy,s) or 0 end

    local p_number_vx_t = Ct { (p_number_vx + p_separator)^1 }
    local p_number_vy_t = Ct { (p_number_vy + p_separator)^1 }

    local zerotable = { 0 }

    asnumber_vx_t = function(s) return s and lpegmatch(p_number_vx_t,s) or zerotable end
    asnumber_vy_t = function(s) return s and lpegmatch(p_number_vy_t,s) or zerotable end

--     local p_numbersep   = p_number_n + p_separator
    local p_numbersep   = p_number_u + p_separator
          p_numbers     = p_optseparator * P("(") * p_numbersep^0 * p_optseparator * P(")")
          p_fournumbers = p_numbersep^4
          p_path        = Ct ( (
          p_command_xy * (p_optseparator * p_number_vx *
                          p_optseparator * p_number_vy )^1
        + p_command_x  * (p_optseparator * p_number_vx )^1
        + p_command_y  * (p_optseparator * p_number_vy )^1
        + p_command_a  * (p_optseparator * p_number_vx *
                          p_optseparator * p_number_vy *
                          p_optseparator * p_number_r  *
                          p_optseparator * p_number_n  * -- flags
                          p_optseparator * p_number_n  * -- flags
                          p_optseparator * p_number_vx *
                          p_optseparator * p_number_vy )^1
        + p_command
        + p_separator
    )^1 )


end

-- We can actually use the svg color definitions from the tex end but maybe a user
-- doesn't want those replace the normal definitions.
--
-- local hexhash  = setmetatableindex(function(t,k) local v = lpegmatch(p_hexcolor, k) t[k] = v return v end)  -- per file
-- local hexhash3 = setmetatableindex(function(t,k) local v = lpegmatch(p_hexcolor3,k) t[k] = v return v end)  -- per file
--
-- local function hexcolor (c) return hexhash [c] end -- directly do hexhash [c]
-- local function hexcolor3(c) return hexhash3[c] end -- directly do hexhash3[c]

local colormap = false

local function prepared(t)
    if type(t) == "table" then
        local mapping  = t.mapping or { }
        local mapper   = t.mapper
        local colormap = setmetatableindex(mapping)
        if mapper then
            setmetatableindex(colormap,function(t,k)
                local v = mapper(k)
                t[k] = v or k
                return v
            end)
        end
        return colormap
    else
        return false
    end
end

local colormaps = setmetatableindex(function(t,k)
    local v = false
    if type(k) == "string" then
        v = prepared(table.load(k)) -- todo: same path as svg file
    elseif type(k) == "table" then
        v = prepared(k)
        k = k.name or k
    end
    t[k] = v
    return v
end)

function metapost.svgcolorremapper(colormap)
    return colormaps[colormap]
end

-- todo: cache colors per image / remapper

local colorcomponents, withcolor, thecolor, usedcolors  do

    local svgcolors = {
        aliceblue       = 0xF0F8FF, antiquewhite      = 0xFAEBD7, aqua                  = 0x00FFFF, aquamarine       = 0x7FFFD4,
        azure           = 0xF0FFFF, beige             = 0xF5F5DC, bisque                = 0xFFE4C4, black            = 0x000000,
        blanchedalmond  = 0xFFEBCD, blue              = 0x0000FF, blueviolet            = 0x8A2BE2, brown            = 0xA52A2A,
        burlywood       = 0xDEB887, cadetblue         = 0x5F9EA0, hartreuse             = 0x7FFF00, chocolate        = 0xD2691E,
        coral           = 0xFF7F50, cornflowerblue    = 0x6495ED, cornsilk              = 0xFFF8DC, crimson          = 0xDC143C,
        cyan            = 0x00FFFF, darkblue          = 0x00008B, darkcyan              = 0x008B8B, darkgoldenrod    = 0xB8860B,
        darkgray        = 0xA9A9A9, darkgreen         = 0x006400, darkgrey              = 0xA9A9A9, darkkhaki        = 0xBDB76B,
        darkmagenta     = 0x8B008B, darkolivegreen    = 0x556B2F, darkorange            = 0xFF8C00, darkorchid       = 0x9932CC,
        darkred         = 0x8B0000, darksalmon        = 0xE9967A, darkseagreen          = 0x8FBC8F, darkslateblue    = 0x483D8B,
        darkslategray   = 0x2F4F4F, darkslategrey     = 0x2F4F4F, darkturquoise         = 0x00CED1, darkviolet       = 0x9400D3,
        deeppink        = 0xFF1493, deepskyblue       = 0x00BFFF, dimgray               = 0x696969, dimgrey          = 0x696969,
        dodgerblue      = 0x1E90FF, firebrick         = 0xB22222, floralwhite           = 0xFFFAF0, forestgreen      = 0x228B22,
        fuchsia         = 0xFF00FF, gainsboro         = 0xDCDCDC, ghostwhite            = 0xF8F8FF, gold             = 0xFFD700,
        goldenrod       = 0xDAA520, gray              = 0x808080, green                 = 0x008000, greenyellow      = 0xADFF2F,
        grey            = 0x808080, honeydew          = 0xF0FFF0, hotpink               = 0xFF69B4, indianred        = 0xCD5C5C,
        indigo          = 0x4B0082, ivory             = 0xFFFFF0, khaki                 = 0xF0E68C, lavender         = 0xE6E6FA,
        lavenderblush   = 0xFFF0F5, lawngreen         = 0x7CFC00, lemonchiffon          = 0xFFFACD, lightblue        = 0xADD8E6,
        lightcoral      = 0xF08080, lightcyan         = 0xE0FFFF, lightgoldenrodyellow  = 0xFAFAD2, lightgray        = 0xD3D3D3,
        lightgreen      = 0x90EE90, lightgrey         = 0xD3D3D3, lightpink             = 0xFFB6C1, lightsalmon      = 0xFFA07A,
        lightseagreen   = 0x20B2AA, lightskyblue      = 0x87CEFA, lightslategray        = 0x778899, lightslategrey   = 0x778899,
        lightsteelblue  = 0xB0C4DE, lightyellow       = 0xFFFFE0, lime                  = 0x00FF00, limegreen        = 0x32CD32,
        linen           = 0xFAF0E6, magenta           = 0xFF00FF, maroon                = 0x800000, mediumaquamarine = 0x66CDAA,
        mediumblue      = 0x0000CD, mediumorchid      = 0xBA55D3, mediumpurple          = 0x9370DB, mediumseagreen   = 0x3CB371,
        mediumslateblue = 0x7B68EE, mediumspringgreen = 0x00FA9A, mediumturquoise       = 0x48D1CC, mediumvioletred  = 0xC71585,
        midnightblue    = 0x191970, mintcream         = 0xF5FFFA, mistyrose             = 0xFFE4E1, moccasin         = 0xFFE4B5,
        navajowhite     = 0xFFDEAD, navy              = 0x000080, oldlace               = 0xFDF5E6, olive            = 0x808000,
        olivedrab       = 0x6B8E23, orange            = 0xFFA500, orangered             = 0xFF4500, orchid           = 0xDA70D6,
        palegoldenrod   = 0xEEE8AA, palegreen         = 0x98FB98, paleturquoise         = 0xAFEEEE, palevioletred    = 0xDB7093,
        papayawhip      = 0xFFEFD5, peachpuff         = 0xFFDAB9, peru                  = 0xCD853F, pink             = 0xFFC0CB,
        plum            = 0xDDA0DD, powderblue        = 0xB0E0E6, purple                = 0x800080, red              = 0xFF0000,
        rosybrown       = 0xBC8F8F, royalblue         = 0x4169E1, saddlebrown           = 0x8B4513, salmon           = 0xFA8072,
        sandybrown      = 0xF4A460, seagreen          = 0x2E8B57, seashell              = 0xFFF5EE, sienna           = 0xA0522D,
        silver          = 0xC0C0C0, skyblue           = 0x87CEEB, slateblue             = 0x6A5ACD, slategray        = 0x708090,
        slategrey       = 0x708090, snow              = 0xFFFAFA, springgreen           = 0x00FF7F, steelblue        = 0x4682B4,
        tan             = 0xD2B48C, teal              = 0x008080, thistle               = 0xD8BFD8, tomato           = 0xFF6347,
        turquoise       = 0x40E0D0, violet            = 0xEE82EE, wheat                 = 0xF5DEB3, white            = 0xFFFFFF,
        whitesmoke      = 0xF5F5F5, yellow            = 0xFFFF00, yellowgreen           = 0x9ACD32,
    }

    local f_rgb      = formatters['    withcolor svgcolor(%.3N,%.3N,%.3N)']
    local f_cmyk     = formatters['    withcolor svgcmyk(%.3N,%.3N,%.3N,%.3N)']
    local f_gray     = formatters['    withcolor svggray(%.3N)']
    local f_rgba     = formatters['    withcolor svgcolor(%.3N,%.3N,%.3N) withopacity %.3N']
    local f_graya    = formatters['    withcolor svggray(%.3N) withopacity %.3N']
    local f_name     = formatters['    withcolor "%s"']
    local f_svgrgb   = formatters['svgcolor(%.3N,%.3N,%.3N)']
    local f_svgcmyk  = formatters['svgcmyk(%.3N,%.3N,%.3N,%.3N)']
    local f_svggray  = formatters['svggray(%.3N)']
    local f_svgname  = formatters['"%s"']

    local triplets = setmetatableindex(function(t,k)
        -- we delay building all these strings
        local v = svgcolors[k]
        if v then
            v = { ((v>>16)&0xFF)/0xFF, ((v>>8)&0xFF)/0xFF, ((v>>0)&0xFF)/0xFF }
        else
            v = false
        end
        t[k] = v
        return v
    end)

    local p_fraction  = C(p_number) * C("%")^-1  / function(a,b) return tonumber(a) / (b and 100 or 255) end
    local p_angle     = C(p_number) * P("deg")^0 / function(a)   return tonumber(a) end
    local p_percent   = C(p_number) * P("%")     / function(a)   return tonumber(a) / 100 end
    local p_absolute  = C(p_number)              / tonumber

    local p_left      = P("(")
    local p_right     = P(")")
    local p_a         = P("a")^-1
    local p_r_a_color = p_left
                      * (p_fraction * p_separator^-1)^-3
                      * p_absolute^0
                      * p_right
    local p_c_k_color = p_left
                      * (p_absolute + p_separator^-1)^-4
                      * p_right
    local p_h_a_color = p_left
                      * p_angle
                      * p_separator   * p_percent
                      * p_separator   * p_percent
                      * p_separator^0 * p_absolute^0
                      * p_right

    local colors      = attributes.colors
    local colorvalues = colors.values
    local colorindex  = attributes.list[attributes.private('color')]
    local hsvtorgb    = colors.hsvtorgb
    local hwbtorgb    = colors.hwbtorgb
    local forcedmodel = colors.forcedmodel

    local function to_rgb(r,g,b)
        return "rgb",
            (r == "00" and 0) or (r == "ff" and 1) or (r == "FF" and 1) or (tonumber(r,16)/255),
            (g == "00" and 0) or (g == "ff" and 1) or (g == "FF" and 1) or (tonumber(g,16)/255),
            (b == "00" and 0) or (b == "ff" and 1) or (b == "FF" and 1) or (tonumber(b,16)/255)
    end

    local function to_gray(s)
        return "gray",
            (s == "00" and 0) or (s == "ff" and 1) or (s == "FF" and 1) or (tonumber(s,16)/255)
    end

    local p_splitcolor =
        P("#") * C(p_hexdigit)^1 / function(a,b,c,d,e,f)
            if not a then
                return "gray", 0
            elseif not b then
                return to_gray(a..a)
            elseif not c then
                return to_gray(a..b)
            elseif not d then
                return to_rgb(a..a,b..b,c..c)
            elseif e and f then
                return to_rgb(a..b,c..d,e..f)
            else
                return "gray", 0
            end
        end
      + P("rgb") * p_a
      * p_r_a_color / function(r,g,b,a)
            return "rgb", r or 0, g or 0, b or 0, a or false
        end
      + P("cmyk")
      * p_c_k_color / function(c,m,y,k)
            return "cmyk", c or 0, m or 0, y or 0, k or 0
        end
      + P("hsl") * p_a
      * p_h_a_color / function(h,s,l,a)
            local r, g, b = hsvtorgb(h,s,l,a)
            return "rgb", r or 0, g or 0, b or 0, a or false
        end
      + P("hwb") * p_a
      * p_h_a_color / function(h,w,b,a)
            local r, g, b = hwbtorgb(h,w,b)
            return "rgb", r or 0, g or 0, b or 0, a or false
        end

    function metapost.svgsplitcolor(color)
        if type(color) == "string" then
            local what, s1, s2, s3, s4 = lpegmatch(p_splitcolor,color)
            if not what then
                local t = triplets[color]
                if t then
                    what, s1, s2, s3 = "rgb", t[1], t[2], t[3]
                end
            end
            return what, s1, s2, s3, s4
        else
            return "gray", 0, false
        end
    end

    local function registeredcolor(name)
        local color = colorindex[name]
        if color then
            local v = colorvalues[color]
            local t = forcedmodel(v[1])
            if t == 2 then
                return "gray", v[2]
            elseif t == 3 then
                return "rgb", v[3], v[4], v[5]
            elseif t == 4 then
                return "cmyk", v[6], v[7], v[8], v[9]
            else
                --
            end
        end
    end

    -- we can have a fast check for #000000

    local function validcolor(color)
        if usedcolors then
            usedcolors[color] = usedcolors[color] + 1
        end
        if colormap then
            local c = colormap[color]
            local t = type(c)
            if t == "table" then
                local what = t[1]
                if what == "rgb" then
                    return
                        what,
                        tonumber(t[2]) or 0,
                        tonumber(t[3]) or 0,
                        tonumber(t[4]) or 0,
                        tonumber(t[5]) or false
                elseif what == "cmyk" then
                    return
                        what,
                        tonumber(t[2]) or 0,
                        tonumber(t[3]) or 0,
                        tonumber(t[4]) or 0,
                        tonumber(t[5]) or 0
                elseif what == "gray" then
                    return
                        what,
                        tonumber(t[2]) or 0,
                        tonumber(t[3]) or false
                end
            elseif t == "string" then
                color = c
            end
        end
        if color == "#000000" then
            return "rgb", 0, 0, 0
        elseif color == "#ffffff" then
            return "rgb", 1, 1, 1
        else
            local what, s1, s2, s3, s4 = registeredcolor(color)
            if not what then
                what, s1, s2, s3, s4 = lpegmatch(p_splitcolor,color)
                -- we could cache
                if not what then
                    local t = triplets[color]
                    if t then
                        s1, s2, s3 = t[1], t[2], t[3]
                        what = "rgb"
                    end
                end
            end
            return what, s1, s2, s3, s4
        end
    end

    colorcomponents = function(color)
        local what, s1, s2, s3, s4 = validcolor(color)
        return s1, s2, s3, s4 -- so 4 means cmyk
    end

    withcolor = function(color)
        local what, s1, s2, s3, s4 = validcolor(color)
        if what == "rgb" then
            if s4 then
                if s1 == s2 and s1 == s3 then
                    return f_graya(s1,s4)
                else
                    return f_rgba(s1,s2,s3,s4)
                end
            else
                if s1 == s2 and s1 == s3 then
                    return f_gray(s1)
                else
                    return f_rgb(s1,s2,s3)
                end
            end
        elseif what == "cmyk" then
            return f_cmyk(s1,s2,s3,s4)
        elseif what == "gray" then
            if s2 then
                return f_graya(s1,s2)
            else
                return f_gray(s1)
            end
        end
        return f_name(color)
    end

    thecolor = function(color)
        local what, s1, s2, s3, s4 = validcolor(color)
        if what == "rgb" then
            if s4 then
                if s1 == s2 and s1 == s3 then
                    return f_svggraya(s1,s4)
                else
                    return f_svgrgba(s1,s2,s3,s4)
                end
            else
                if s1 == s2 and s1 == s3 then
                    return f_svggray(s1)
                else
                    return f_svgrgb(s1,s2,s3)
                end
            end
        elseif what == "cmyk" then
            return f_cmyk(s1,s2,s3,s4)
        elseif what == "gray" then
            if s2 then
                return f_svggraya(s1,s2)
            else
                return f_svggray(s1)
            end
        end
        return f_svgname(color)
    end

end

-- actually we can loop faster because we can go to the last one

local grabpath, grablist  do

    local f_moveto    = formatters['(%N,%N)']
    local f_curveto_z = formatters['controls(%N,%N)and(%N,%N)..(%N,%N)']
    local f_curveto_n = formatters['..controls(%N,%N)and(%N,%N)..(%N,%N)']
    local f_lineto_z  = formatters['(%N,%N)']
    local f_lineto_n  = formatters['--(%N,%N)']

    local m = { __index = function() return 0 end }

 -- local t      = { }    -- no real saving here if we share
 -- local n      = 0

    grabpath = function(str)
        local p   = lpegmatch(p_path,str) or { }
        local np  = #p
        local all = { entries = np, closed = false, curve = false }
        if np == 0 then
            return all
        end
        setmetatable(p,m)
        local t      = { }    -- no real saving here if we share
        local n      = 0
        local i      = 0
        local last   = "M"
        local prev   = last
        local kind   = "L"
        local x, y   = 0, 0
        local x1, y1 = 0, 0
        local x2, y2 = 0, 0
        local rx, ry = 0, 0
        local ar, al = 0, 0
        local as, ac = 0, nil
        local mx, my = 0, 0
        while i < np do
            i = i + 1
            local pi = p[i]
            if type(pi) ~= "number" then
                last = pi
                i    = i + 1
                pi   = p[i]
            end
            -- most often
            if last == "c" then
                            x1 = x + pi
                i = i + 1 ; y1 = y + p[i]
                i = i + 1 ; x2 = x + p[i]
                i = i + 1 ; y2 = y + p[i]
                i = i + 1 ; x  = x + p[i]
                i = i + 1 ; y  = y + p[i]
                goto curveto
            elseif last == "l" then
                            x = x + pi
                i = i + 1 ; y = y + p[i]
                goto lineto
            elseif last == "h" then
                x = x + pi
                goto lineto
            elseif last == "v" then
                y = y + pi
                goto lineto
            elseif last == "a" then
                            x1 =     x
                            y1 =     y
                            rx =     pi
                i = i + 1 ; ry =     p[i]
                i = i + 1 ; ar =     p[i]
                i = i + 1 ; al =     p[i]
                i = i + 1 ; as =     p[i]
                i = i + 1 ; x  = x + p[i]
                i = i + 1 ; y  = y + p[i]
                goto arc
            elseif last == "s" then
                if prev == "C" then
                    x1 = 2 * x - x2
                    y1 = 2 * y - y2
                else
                    x1 = x
                    y1 = y
                end
                            x2 = x + pi
                i = i + 1 ; y2 = y + p[i]
                i = i + 1 ; x  = x + p[i]
                i = i + 1 ; y  = y + p[i]
                goto curveto
            elseif last == "m" then
                            x = x + pi
                i = i + 1 ; y = y + p[i]
                goto moveto
            elseif last == "z" then
                goto close
            -- less frequent
            elseif last == "C" then
                            x1 = pi
                i = i + 1 ; y1 = p[i]
                i = i + 1 ; x2 = p[i]
                i = i + 1 ; y2 = p[i]
                i = i + 1 ; x  = p[i]
                i = i + 1 ; y  = p[i]
                goto curveto
            elseif last == "L" then
                            x = pi
                i = i + 1 ; y = p[i]
                goto lineto
            elseif last == "H" then
                x = pi
                goto lineto
            elseif last == "V" then
                y = pi
                goto lineto
            elseif last == "A" then
                            x1 = x
                            y1 = y
                            rx = pi
                i = i + 1 ; ry = p[i]
                i = i + 1 ; ar = p[i]
                i = i + 1 ; al = p[i]
                i = i + 1 ; as = p[i]
                i = i + 1 ; x  = p[i]
                i = i + 1 ; y  = p[i]
                goto arc
            elseif last == "S" then
                if prev == "C" then
                    x1 = 2 * x - x2
                    y1 = 2 * y - y2
                else
                    x1 = x
                    y1 = y
                end
                            x2 = pi
                i = i + 1 ; y2 = p[i]
                i = i + 1 ; x  = p[i]
                i = i + 1 ; y  = p[i]
                goto curveto
            elseif last == "M" then
                            x = pi ;
                i = i + 1 ; y = p[i]
                goto moveto
            elseif last == "Z" then
                goto close
            -- very seldom
            elseif last == "q" then
                            x1 = x + pi
                i = i + 1 ; y1 = y + p[i]
                i = i + 1 ; x2 = x + p[i]
                i = i + 1 ; y2 = y + p[i]
                goto quadratic
            elseif last == "t" then
                if prev == "C" then
                    x1 = 2 * x - x1
                    y1 = 2 * y - y1
                else
                    x1 = x
                    y1 = y
                end
                            x2 = x + pi
                i = i + 1 ; y2 = y + p[i]
                goto quadratic
            elseif last == "Q" then
                            x1 = pi
                i = i + 1 ; y1 = p[i]
                i = i + 1 ; x2 = p[i]
                i = i + 1 ; y2 = p[i]
                goto quadratic
            elseif last == "T" then
                if prev == "C" then
                    x1 = 2 * x - x1
                    y1 = 2 * y - y1
                else
                    x1 = x
                    y1 = y
                end
                            x2 = pi
                i = i + 1 ; y2 = p[i]
                goto quadratic
            else
                goto continue
            end
            ::moveto::
                if n > 0 then
                    n = n + 1 ; t[n] = "&&"
                end
                n = n + 1 ; t[n] = f_moveto(x,y)
                last = last == "M" and "L" or "l"
                prev = "M"
                mx = x
                my = y
                goto continue
            ::lineto::
                n = n + 1 ; t[n] = (n > 0 and f_lineto_n or f_lineto_z)(x,y)
                prev = "L"
                goto continue
            ::curveto::
                n = n + 1 ; t[n] = (n > 0 and f_curveto_n or f_curveto_z)(x1,y1,x2,y2,x,y)
                prev = "C"
                goto continue
            ::arc::
                ac = a2c(x1,y1,rx,ry,ar,al,as,x,y)
                for i=1,#ac,6 do
                    n = n + 1 ; t[n] = (n > 0 and f_curveto_n or f_curveto_z)(
                        ac[i],ac[i+1],ac[i+2],ac[i+3],ac[i+4],ac[i+5]
                    )
                end
                prev = "A"
                goto continue
            ::quadratic::
                n = n + 1 ; t[n] = (n > 0 and f_curveto_n or f_curveto_z)(
                    x  + 2/3 * (x1-x ), y  + 2/3 * (y1-y ),
                    x2 + 2/3 * (x1-x2), y2 + 2/3 * (y1-y2),
                    x2,                 y2
                )
                x = x2
                y = y2
                prev = "C"
                goto continue
            ::close::
                if i == np then
                    break
                else
                    i = i - 1
                end
                kind = prev
                prev = "Z"
                -- this is kind of undocumented: a close also moves back
                x = mx
                y = my
            ::continue::
        end
        if n > 0 then
            if prev == "Z" then
                all.closed = true
                n = n + 1 ; t[n] = "&&cycle"
            end
            all.path = concat(t,"",1,n)
        end
        all.curve = kind == "C" or kind == "A"
        return all, p
    end

    -- this is a bit tricky as what are points for a mark ... the next can be simplified
    -- a lot

    grablist = function(p)
        local np  = #p
        if np == 0 then
            return nil
        end
        local t      = { }
        local n      = 0
        local a      = 0
        local i      = 0
        local last   = "M"
        local prev   = last
        local kind   = "L"
        local x, y   = 0, 0
        local x1, y1 = 0, 0
        local x2, y2 = 0, 0
        local rx, ry = 0, 0
        local ar, al = 0, 0
        local as, ac = 0, nil
        local mx, my = 0, 0
        while i < np do
            i = i + 1
            local pi = p[i]
            if type(pi) ~= "number" then
                last = pi
                i    = i + 1
                pi   = p[i]
            end
            -- most often
            if last == "c" then
                            x1 = x + pi
                i = i + 1 ; y1 = y + p[i]
                i = i + 1 ; x2 = x + p[i]
                i = i + 1 ; y2 = y + p[i]
                i = i + 1 ; x  = x + p[i]
                i = i + 1 ; y  = y + p[i]
                goto curveto
            elseif last == "l" then
                            x = x + pi
                i = i + 1 ; y = y + p[i]
                goto lineto
            elseif last == "h" then
                x = x + pi
                goto lineto
            elseif last == "v" then
                y = y + pi
                goto lineto
            elseif last == "a" then
                            x1 =     x
                            y1 =     y
                            rx =     pi
                i = i + 1 ; ry =     p[i]
                i = i + 1 ; ar =     p[i]
                i = i + 1 ; al =     p[i]
                i = i + 1 ; as =     p[i]
                i = i + 1 ; x  = x + p[i]
                i = i + 1 ; y  = y + p[i]
                goto arc
            elseif last == "s" then
                if prev == "C" then
                    x1 = 2 * x - x2
                    y1 = 2 * y - y2
                else
                    x1 = x
                    y1 = y
                end
                            x2 = x + pi
                i = i + 1 ; y2 = y + p[i]
                i = i + 1 ; x  = x + p[i]
                i = i + 1 ; y  = y + p[i]
                goto curveto
            elseif last == "m" then
                            x = x + pi
                i = i + 1 ; y = y + p[i]
                goto moveto
            elseif last == "z" then
                goto close
            -- less frequent
            elseif last == "C" then
                            x1 = pi
                i = i + 1 ; y1 = p[i]
                i = i + 1 ; x2 = p[i]
                i = i + 1 ; y2 = p[i]
                i = i + 1 ; x  = p[i]
                i = i + 1 ; y  = p[i]
                goto curveto
            elseif last == "L" then
                            x = pi
                i = i + 1 ; y = p[i]
                goto lineto
            elseif last == "H" then
                x = pi
                goto lineto
            elseif last == "V" then
                y = pi
                goto lineto
            elseif last == "A" then
                            x1 = x
                            y1 = y
                            rx = pi
                i = i + 1 ; ry = p[i]
                i = i + 1 ; ar = p[i]
                i = i + 1 ; al = p[i]
                i = i + 1 ; as = p[i]
                i = i + 1 ; x  = p[i]
                i = i + 1 ; y  = p[i]
                goto arc
            elseif last == "S" then
                if prev == "C" then
                    x1 = 2 * x - x2
                    y1 = 2 * y - y2
                else
                    x1 = x
                    y1 = y
                end
                            x2 = pi
                i = i + 1 ; y2 = p[i]
                i = i + 1 ; x  = p[i]
                i = i + 1 ; y  = p[i]
                goto curveto
            elseif last == "M" then
                            x = pi ;
                i = i + 1 ; y = p[i]
                goto moveto
            elseif last == "Z" then
                goto close
            -- very seldom
            elseif last == "q" then
                            x1 = x + pi
                i = i + 1 ; y1 = y + p[i]
                i = i + 1 ; x2 = x + p[i]
                i = i + 1 ; y2 = y + p[i]
                goto quadratic
            elseif last == "t" then
                if prev == "C" then
                    x1 = 2 * x - x1
                    y1 = 2 * y - y1
                else
                    x1 = x
                    y1 = y
                end
                            x2 = x + pi
                i = i + 1 ; y2 = y + p[i]
                goto quadratic
            elseif last == "Q" then
                            x1 = pi
                i = i + 1 ; y1 = p[i]
                i = i + 1 ; x2 = p[i]
                i = i + 1 ; y2 = p[i]
                goto quadratic
            elseif last == "T" then
                if prev == "C" then
                    x1 = 2 * x - x1
                    y1 = 2 * y - y1
                else
                    x1 = x
                    y1 = y
                end
                            x2 = pi
                i = i + 1 ; y2 = p[i]
                goto quadratic
            else
                goto continue
            end
            ::moveto::
                n = n + 1 ; t[n] = x
                n = n + 1 ; t[n] = y
                last = last == "M" and "L" or "l"
                prev = "M"
                mx = x
                my = y
                goto continue
            ::lineto::
                n = n + 1 ; t[n] = x
                n = n + 1 ; t[n] = y
                prev = "L"
                goto continue
            ::curveto::
                n = n + 1 ; t[n] = x
                n = n + 1 ; t[n] = y
                prev = "C"
                goto continue
            ::arc::
                ac = a2c(x1,y1,rx,ry,ar,al,as,x,y)
                for i=1,#ac,6 do
                    n = n + 1 ; t[n] = ac[i+4]
                    n = n + 1 ; t[n] = ac[i+5]
                end
                prev = "A"
                goto continue
            ::quadratic::
                n = n + 1 ; t[n] = x2
                n = n + 1 ; t[n] = y2
                x = x2
                y = y2
                prev = "C"
                goto continue
            ::close::
                n = n + 1 ; t[n] = mx
                n = n + 1 ; t[n] = my
                if i == np then
                    break
                end
                kind = prev
                prev = "Z"
                x = mx
                y = my
            ::continue::
        end
        return t
    end

end

-- todo: viewbox helper

local s_wrapped_start <const> = "draw image ("
local f_wrapped_stop          = formatters[") shifted (0,%N) scaled %N ;"]

local handletransform, handletransformstring, handleviewbox  do

    local sind = math.sind

    local f_rotatedaround           = formatters["rotatedaround((%N,%N),%N) "]
    local f_rotated                 = formatters["rotated(%N) "]
    local f_shifted                 = formatters["shifted(%N,%N) "]
    local f_slanted_x               = formatters["xslanted(%N) "]
    local f_slanted_y               = formatters["yslanted(%N) "]
    local f_scaled                  = formatters["scaled(%N) "]
    local f_xyscaled                = formatters["xyscaled(%N,%N) "]
    local f_matrix                  = formatters["transformed bymatrix(%N,%N,%N,%N,%N,%N) "]
    local s_transform_start <const> = "draw image ( "
    local f_transform_stop          = formatters[") %s ; "]

    local transforms    = { }
    local noftransforms = 0

    local function rotate(r,x,y)
        if r then
            noftransforms = noftransforms + 1
            if x then
                transforms[noftransforms] = f_rotatedaround(x,-(y or x),-r)
            else
                transforms[noftransforms] = f_rotated(-r)
            end
        end
    end

    local function translate(x,y)
        if x == 0 then x = false end
        if y == 0 then y = false end
        if y then
            noftransforms = noftransforms + 1
            transforms[noftransforms] = f_shifted(x or 0,-y)
        elseif x then
            noftransforms = noftransforms + 1
            transforms[noftransforms] = f_shifted(x,0)
        end
    end

    local function scale(x,y)
        if x == 1 then x = false end
        if y == 1 then y = false end
        if y then
            noftransforms = noftransforms + 1
            transforms[noftransforms] = f_xyscaled(x or 1,y)
        elseif x then
            noftransforms = noftransforms + 1
            transforms[noftransforms] = f_scaled(x)
        end
    end

    local function skew(x,y)
     -- if x = 0 then x = false end
     -- if y = 0 then y = false end
        if x then
            noftransforms = noftransforms + 1
            transforms[noftransforms] = f_slanted_x(sind(-x))
        end
        if y then
            noftransforms = noftransforms + 1
            transforms[noftransforms] = f_slanted_y(sind(-y))
        end
    end

    local function matrix(rx,sx,sy,ry,tx,ty)
        if not ty then
            ty = 0
        end
        if not tx then
            tx = 0
        end
        if not sx then
            sx = 0
        end
        if not sy then
            sy = 0
        end
        if not rx then
            rx = 1
        end
        if not ry then
            ry = 1
        end
        noftransforms = noftransforms + 1
     -- transforms[noftransforms] = f_matrix(rx, sx, sy, ry, tx, -ty)
        -- https://en.wikipedia.org/wiki/Rotation_matrix : we're counter clockwise
        transforms[noftransforms] = f_matrix(rx, -sy, -sx, ry, tx, -ty)
    end

    local p_transform = (
            p_space^0 * (
            P("translate")  * (p_numbers / translate)      -- maybe xy
          + P("scale")      * (p_numbers / scale)
          + P("rotate")     * (p_numbers / rotate)
          + P("matrix")     * (p_numbers / matrix)
          + P("skew")       * (p_numbers / skew)
          + P("translateX") * (p_numbers / translate)
          + P("translateY") * (Cc(false) * p_numbers / translate)
          + P("scaleX")     * (p_numbers / translate)
          + P("scaleY")     * (Cc(false) * p_numbers / translate)
          + P("skewX")      * (p_numbers / skew)
          + P("skewY")      * (Cc(false) * p_numbers / skew)
        )
    )^1

    -- indeed, we need to reverse the order ... not that pretty and counter intuitive too

    local function combined()
        if noftransforms == 1 then
            return transforms[1]
        elseif noftransforms == 2 then
            return transforms[2] .. transforms[1]
        elseif noftransforms == 3 then
            return transforms[3] .. transforms[2] .. transforms[1]
        else
            -- the rare case (but anything can happen in svg and it gets worse)
            local m = noftransforms + 1
            for i=1,noftransforms//2 do
                local j = m - i
                transforms[i], transforms[j] = transforms[j], transforms[i]
            end
            return concat(transforms,"",1,noftransforms)
        end
    end

    handletransform = function(at,unwrapped)
        local t = at.transform
        if t then
            noftransforms = 0
            lpegmatch(p_transform,t)
            if noftransforms > 0 then
                -- currentpicture
                return s_transform_start, f_transform_stop(combined()), t
            end
        end
    end

    handletransformstring = function(s)
        if s then
            noftransforms = 0
            lpegmatch(p_transform,s)
            return noftransforms > 0 and combined()
        end
    end

    handleviewbox = function(v)
        if v then
            local x, y, w, h = lpegmatch(p_fournumbers,v)
            if h then
                return x, y, w, h
            end
        end
    end

end

local dashed  do

    -- actually commas are mandate but we're tolerant

    local f_dashed_n = formatters[" dashed dashpattern (%s ) "]
    local f_dashed_y = formatters[" dashed dashpattern (%s ) shifted (%N,0) "]

    local p_number   = p_optseparator/"" * p_number_r
    local p_on       = Cc(" on ")  * p_number
    local p_off      = Cc(" off ") * p_number
    local p_dashed   = Cs((p_on * p_off^-1)^1)

    dashed = function(s,o)
        if not find(s,",") then
            -- a bit of a hack:
            s = s .. " " .. s
        end
        return (o and f_dashed_y or f_dashed_n)(lpegmatch(p_dashed,s),o)
    end

end

local handledefinitions do

    local handlers    = { }
    local process     = false
    local root        = false
    local result      = false
    local r           = false
    local definitions = false
    local classstyles = false
    local tagstyles   = false
 -- local colormap    = false

    local tags = {
        ["a"]                  = true,
     -- ["altgGlyph"]          = true,
     -- ["altgGlyphDef"]       = true,
     -- ["altgGlyphItem"]      = true,
     -- ["animate"]            = true,
     -- ["animateColor"]       = true,
     -- ["animateMotion"]      = true,
     -- ["animateTransform"]   = true,
        ["circle"]             = true,
        ["clipPath"]           = true,
     -- ["color-profile"]      = true,
     -- ["cursor"]             = true,
        ["defs"]               = true,
     -- ["desc"]               = true,
        ["ellipse"]            = true,
     -- ["filter"]             = true,
     -- ["font"]               = true,
     -- ["font-face"]          = true,
     -- ["font-face-format"]   = true,
     -- ["font-face-name"]     = true,
     -- ["font-face-src"]      = true,
     -- ["font-face-uri"]      = true,
     -- ["foreignObject"]      = true,
        ["g"]                  = true,
     -- ["glyph"]              = true,
     -- ["glyphRef"]           = true,
     -- ["hkern"]              = true,
        ["image"]              = true,
        ["line"]               = true,
        ["linearGradient"]     = true,
        ["marker"]             = true,
     -- ["mask"]               = true,
     -- ["metadata"]           = true,
     -- ["missing-glyph"]      = true,
     -- ["mpath"]              = true,
        ["path"]               = true,
        ["pattern"]            = true,
        ["polygon"]            = true,
        ["polyline"]           = true,
        ["radialGradient"]     = true,
        ["rect"]               = true,
     -- ["script"]             = true,
     -- ["set"]                = true,
        ["stop"]               = true,
        ["style"]              = true,
        ["svg"]                = true,
     -- ["switch"]             = true,
        ["symbol"]             = true,
        ["text"]               = true,
     -- ["textPath"]           = true,
     -- ["title"]              = true,
        ["tspan"]              = true,
        ["use"]                = true,
     -- ["view"]               = true,
     -- ["vkern"]              = true,
    }

    local usetags = {
        ["circle"]             = true,
        ["ellipse"]            = true,
        ["g"]                  = true,
        ["image"]              = true,
        ["line"]               = true,
        ["path"]               = true,
        ["polygon"]            = true,
        ["polyline"]           = true,
        ["rect"]               = true,
     -- ["text"]               = true,
     -- ["tspan"]              = true,
    }

    local pathtracer = {
        ["stroke"]         = "darkred",
        ["stroke-opacity"] = ".5",
        ["stroke-width"]   = ".5",
        ["fill"]           = "darkgray",
        ["fill-opacity"]   = ".75",
    }

    local skipspace  = p_space^0
    local colon      = P(":")
    local semicolon  = P(";")
    local eos        = P(-1)

    local someaction = (
        skipspace * C((1 - (skipspace * (semicolon + eos + colon)))^1)
      * colon
      * skipspace * C((1 - (skipspace * (semicolon + eos)))^0)
      * Carg(1) / function(k,v,a) a[k] = v end
        + (p_space + semicolon)^1
     )^1

    local function handlechains(c)
        if tags[c.tg] then
            local at = c.at
            local dt = c.dt
            if at and dt then
             -- at["inkscape:connector-curvature"] = nil -- clear entry and might prevent table growth
                local estyle = rawget(at,"style")
                if estyle and estyle ~= "" then
                    lpegmatch(someaction,estyle,1,at)
                end
                local eclass = rawget(at,"class")
                if eclass and eclass ~= "" then
                    for c in gmatch(eclass,"[^ ]+") do
                        local s = classstyles[c]
                        if s then
                            for k, v in next, s do
                                at[k] = v
                            end
                        end
                    end
                end
                local tstyle = tagstyles[tag]
                if tstyle then
                    for k, v in next, tstyle do
                        at[k] = v
                    end
                end
                if trace_path and pathtracer then
                    for k, v in next, pathtracer do
                        at[k] = v
                    end
                end
                for i=1,#dt do
                    local dti = dt[i]
                    if type(dti) == "table" then
                        handlechains(dti)
                    end
                end
            end
        end
    end

    local handlestyle  do

        -- It can also be CDATA but that is probably dealt with because we only
        -- check for style entries and ignore the rest. But maybe we also need
        -- to check a style at the outer level?

        local p_key   = C((R("az","AZ","09","__","--")^1))
        local p_spec  = P("{") * C((1-P("}"))^1) * P("}")
        local p_valid = Carg(1) * P(".") * p_key + Carg(2) * p_key
        local p_grab  = ((p_valid * p_space^0 * p_spec / rawset) + p_space^1 + P(1))^1

        local fontspecification = css.fontspecification

        handlestyle = function(c)
            local s = xmltext(c)
            lpegmatch(p_grab,s,1,classstyles,tagstyles)
            for k, v in next, classstyles do
                local t = { }
                for k, v in gmatch(v,"%s*([^:]+):%s*([^;]+);?") do
                    if k == "font" then
                        local s = fontspecification(v)
                        for k, v in next, s do
                            t["font-"..k] = v
                        end
                    else
                        t[k] = v
                    end
                end
                classstyles[k] = t
            end
            for k, v in next, tagstyles do
                local t = { }
                for k, v in gmatch(v,"%s*([^:]+):%s*([^;]+);?") do
                    if k == "font" then
                        local s = fontspecification(v)
                        for k, v in next, s do
                            t["font-"..k] = v
                        end
                    else
                        t[k] = v
                    end
                end
                tagstyles[k] = t
            end
        end

        function handlers.style()
            -- ignore
        end

    end

    -- We can have root in definitions and then do a metatable lookup but use
    -- is not used that often I guess.

    local function locate(id,c)
        if id == none then
            return
        end
        local res = definitions[id]
        local ref
        if res then
            return res
        end
        ref = gsub(id,"^url%(#(.-)%)$","%1")
        ref = gsub(ref,"^#","")
        -- we can make a fast id lookup
        res = xmlfirst(root,"**[@id='"..ref.."']")
        if res then
            definitions[id] = res
            return res
        end
        -- we expect resource paths to be specified but for now we want
        -- them on the same path .. we could use the url splitter .. todo
        ref = url.hashed(id)
        if not ref.nosheme and ref.scheme == "file" then
            local filename = ref.filename
            local fragment = ref.fragment
            if filename and filename ~= "" then
                local fullname = resolvers.findbinfile(filename)
                if isfile(fullname) then
                    report("loading use file: %s",fullname)
                    local root = xml.load(fullname)
                    res = xmlfirst(root,"**[@id='"..fragment.."']")
                    if res and c then
                        xmlinheritattributes(res,c) -- tricky
                        setmetatableindex(res.at,c.at)
                        definitions[id] = res
                        return res
                    end
                end
            end
        end
        return res
    end

    -- also locate

    local function handleclippath(at)
        local clippath = at["clip-path"]

        if not clippath or clippath == "none" then
            return
        end

        local spec = definitions[clippath] or locate(clippath)

        -- do we really need this crap
        if not spec then
            local index = match(clippath,"(%d+)")
            if index then
                spec = xmlfirst(root,"clipPath["..tostring(tonumber(index) or 0).."]")
            end
        end
        -- so far for the crap

        if not spec then
            report("unknown clip %a",clippath)
            return
        elseif spec.tg ~= "clipPath" then
            report("bad clip %a",clippath)
            return
        end

      ::again::
        for c in xmlcollected(spec,"/(path|use|g)") do
            local tg = c.tg
            if tg == "use" then
                local ca = c.at
                local id = ca["xlink:href"]
                if id then
                    spec = locate(id)
                    if spec then
                        local sa = spec.at
                        setmetatableindex(sa,ca)
                        if spec.tg == "path" then
                            local d = sa.d
                            if d then
                                local p = grabpath(d)
                                p.evenodd = sa["clip-rule"] == "evenodd"
                                p.close = true
-- local transform = rawget(ca,"transform")
                                return p, clippath, transform
                            else
                                return
                            end
                        else
                            goto again
                        end
                    end
                end
             -- break
            elseif tg == "path" then
                local ca = c.at
                local d  = rawget(ca,"d")
                if d then
                    local p = grabpath(d)
                    p.evenodd = ca["clip-rule"] == "evenodd"
                    p.close   = true
                    local transform = rawget(ca,"transform")
                    if transform then
                        transform = handletransformstring(transform)
                    end

                    local tr = rawget(at,"transform")
                    if tr then
                        tr = handletransformstring(tr)
                        if tr then
                            if transform then
                                transform = transform .. " " .. tr
                            else
                                transform = tr
                            end
                        end
                    end

                    return p, clippath, transform
                else
                    return
                end
            else
                -- inherit?
            end
        end
    end

    -- todo: clip = [ auto | rect(llx,lly,urx,ury) ]

    local s_rotation_start <const> = "draw image ( "
    local f_rotation_stop          = formatters[") rotatedaround((0,0),-angle((%N,%N))) ;"]
    local f_rotation_angle         = formatters[") rotatedaround((0,0),-%N) ;"]

    local s_offset_start   <const> = "draw image ( "
    local f_offset_stop            = formatters[") shifted (%N,%N) ;"]
    local s_size_start     <const> = "draw image ( "
    local f_size_stop              = formatters[") xysized (%N,%N) ;"]

    local handleoffset, handlesize do

        handleoffset = function(at)
            local x = asnumber_vx(rawget(at,"x"))
            local y = asnumber_vy(rawget(at,"y"))
            if x ~= 0 or y ~= 0 then
                return s_offset_start, f_offset_stop(x,y)
            end
        end

        handlesize = function(at)
            local width  = asnumber_x(rawget(at,"width"))
            local height = asnumber_y(rawget(at,"height"))
            if width == 0 or height == 0 then
                -- bad scaling
            elseif width == 1 and height == 1 then
                -- no need for scaling
            else
                return s_size_start, f_size_stop(width,height)
            end
        end

    end

    function handlers.symbol(c)
        local at  = c.at
        -- x y refX refY
        local boffset, eoffset = handleoffset(at)
        local bsize, esize = handlesize(at)
        local btransform, etransform, transform = handletransform(at)

        if boffset then
            r = r + 1 result[r] = boffset
        end
        if btransform then
            r = r + 1 result[r] = btransform
        end
        if bsize then
            r = r + 1 ; result[r] = bsize
        end

-- local _x = at.x       at.x      = 0
-- local _y = at.y       at.y      = 0
-- local _w = at.width   at.width  = 0
-- local _h = at.height  at.height = 0

        process(c,"/*")
-- at.x      = _x
-- at.y      = _y
-- at.width  = _w
-- at.height = _h

        if esize then
            r = r + 1 result[r] = esize
        end
        if etransform then
            r = r + 1 ; result[r] = etransform
        end
        if eoffset then
            r = r + 1 result[r] = eoffset
        end
    end

    -- do

    local s_shade_linear   =            '    withshademethod "linear" '
    local s_shade_circular =            '    withshademethod "circular" '
    local f_color          = formatters['    withcolor "%s"']
    local f_opacity        = formatters['    withopacity %N']
    local f_pen            = formatters['    withpen pencircle scaled %N']

    -- this is rather hard to deal with because browsers differ (at the time of writing)
    -- and what they show on screen comes out different (or not at all) in print

    -- todo: gradientUnits = "userSpaceOnUse" : use units instead of ratios

    -- spreadMethod = "pad"     : default
    -- spreadMethod = "repeat"  : crap
    -- spreadMethod = "reflect" : crap

    -- stop-opacity = "0"       : strange, just use steps for that

    -- todo: test for kind independently in caller, make a plug instead

    local function pattern(id)
        local c = definitions[id] -- no locate !
        if c and c.tg == "pattern" then
            -- just use result and then prune
            local _r      = r
            local _result = result
            r      = 0
            result = { }
            --
         -- handlers.pattern(spec)
            --
            -- inlined because of width
            --
            local at  = c.at

            local width  = asnumber_x(rawget(at,"width"))
            local height = asnumber_y(rawget(at,"height"))
            if width == 0 or height == 0 then
                -- bad scaling
                width  = nil
                height = nil
            elseif width == 1 and height == 1 then
                -- no need for scaling
                width  = nil
                height = nil
            else
                -- for now only relative
            end

            local boffset, eoffset = handleoffset(at)
         -- local bsize, esize = handlesize(at)
            local btransform, etransform, transform = handletransform(at)

            if boffset then
                r = r + 1 result[r] = boffset
            end
            if btransform then
                r = r + 1 result[r] = btransform
            end
         -- if bsize then
         --     r = r + 1 ; result[r] = bsize
         -- end

            local _x = at.x       at.x      = 0
            local _y = at.y       at.y      = 0
            local _w = at.width   at.width  = 0
            local _h = at.height  at.height = 0

            process(c,"/*")

            at.x      = _x
            at.y      = _y
            at.width  = _w
            at.height = _h

         -- if esize then
         --     r = r + 1 result[r] = esize
         -- end
            if etransform then
                r = r + 1 ; result[r] = etransform
            end
            if eoffset then
                r = r + 1 result[r] = eoffset
            end
            --
            local okay
            if width and height then
                okay = formatters["    withpattern image ( % t )\n    withpatternscale(%N,%N)"](result,width,height)
            else
                okay = formatters["    withpattern image ( % t )"](result)
            end
            r      = _r
            result = _result
            return okay
        end
    end

    local gradient do

        local f_shade_step         = formatters['    withshadestep ( withshadefraction %N withshadecolors (%s,%s) )']
        local f_shade_colors       = formatters['    withshadecolors (%s,%s)']
        local f_shade_center       = formatters['    withshadecenter (%N,%N)']
        local f_shade_center_f     = formatters['    withshadecenterfraction (%N,%N)']
        local f_shade_radius       = formatters['    withshaderadius (%N,%N) ']
        local f_shade_radius_f     = formatters['    withshaderadiusfraction %N']
        local f_shade_center_one   = formatters['    withshadecenterone (%N,%N)']
        local f_shade_center_two   = formatters['    withshadecentertwo (%N,%N)']
        local f_shade_center_one_f = formatters['    withshadecenteronefraction (%N,%N)']
        local f_shade_center_two_f = formatters['    withshadecentertwofraction (%N,%N)']
        local f_shade_transform    = formatters['    withshadetransformation identity %s']

        local f_shade_inverse      = formatters[' transformed inverse (identity %s)']
--         local f_shade_inverse      = formatters[' transformed inverse identity %s'] -- or this?

        gradient = function(id)
            local spec = definitions[id] -- no locate !
            if spec then
                local kind  = spec.tg
                local shade = { }
                local opacs = nil
                local ns    = 0
                local no    = 0
                local a     = spec.at
                -- bah
                local hid = rawget(a,"href") or rawget(a,"xlink:href") -- better a rawget
                local res = locate(hid)
                -- bah
                local gu = rawget(a, "gradientUnits")         -- userSpaceOnUse
                local gt = rawget(a, "gradientTransform")
                local sm = rawget(a, "spreadMethod")
                --
                local userspace = gu == "userSpaceOnUse"
                --
                if kind == "linearGradient" then
                    ns = ns + 1 ; shade[ns] = s_shade_linear
                    --
                    local x1 = rawget(a,"x1")
                    local y1 = rawget(a,"y1")
                    local x2 = rawget(a,"x2")
                    local y2 = rawget(a,"y2")
                    if userspace then
                        if x1 and y1 then
                            ns = ns + 1 ; shade[ns] = f_shade_center_one(asnumber_p(x1),asnumber_p(y1))
                        end
                        if x2 and y2 then
                            ns = ns + 1 ; shade[ns] = f_shade_center_two(asnumber_p(x2),asnumber_p(y2))
                        end
                    else
                        if x1 and y1 then
                            ns = ns + 1 ; shade[ns] = f_shade_center_one_f(asnumber_p(x1),1-asnumber_p(y1))
                        end
                        if x2 and y2 then
                            ns = ns + 1 ; shade[ns] = f_shade_center_two_f(asnumber_p(x2),1-asnumber_p(y2))
                        end
                    end
                    --
                elseif kind == "radialGradient" then
                    ns = ns + 1 ; shade[ns] = s_shade_circular
                    --
                    local cx = rawget(a,"cx") -- x center
                    local cy = rawget(a,"cy") -- y center
                    local r  = rawget(a,"r" ) -- radius
                    local fx = rawget(a,"fx") -- focal points
                    local fy = rawget(a,"fy") -- focal points
                    --
                    if userspace then
                        if cx and cy then
                            ns = ns + 1 ; shade[ns] = f_shade_center(asnumber_p(cx),asnumber_p(cy))
                        end
                        if fx and fy then
                            ns = ns + 1 ; shade[ns] = f_shade_center_one(asnumber_p(fx),-asnumber_p(fy))
                        end
                        if r then
                            ns = ns + 1 ; shade[ns] = f_shade_radius(asnumber_p(r))
                        end
                        if fx and fy then
                            -- todo
                        end
                    else
                        if cx and cy then
                            ns = ns + 1 ; shade[ns] = f_shade_center_f(asnumber_p(cx),1-asnumber_p(cy))
                        end
                        if fx and fy then
                            ns = ns + 1 ; shade[ns] = f_shade_center_one_f(asnumber_p(fx),1-asnumber_p(fy))
                        end
                        if r then
                            ns = ns + 1 ; shade[ns] = f_shade_radius_f(asnumber_p(r))
                        end
                        if fx and fy then
                            -- todo
                        end
                    end
                else
                    return
                end
                -- startcolor ?
                local function crap(spec)
                    local colora, colorb
                    local opacitya, opacityb
                    local lastns = ns
                    for c in xmlcollected(spec,"/stop") do
                        local a      = c.at
                        local offset = rawget(a,"offset")
                        local colorb = rawget(a,"stop-color")
                        if not colora then
                            colora = colorb
                        end
                        -- what if no percentage
                     -- local fraction = offset and asnumber_r(offset) -- asnumber_p ?
                        local fraction = offset and asnumber_p(offset)
                        if not fraction then
                            -- for now
                            fraction = xmlcount(spec,"/stop")/100      -- asnumber_p ?
                        end
                        if colora and colorb and colora ~= "" and colorb ~= "" then
                            ns = ns + 1
                            if userspace then -- todo
                                shade[ns] = f_shade_step(fraction,thecolor(colora),thecolor(colorb))
                            else
                                shade[ns] = f_shade_step(fraction,thecolor(colora),thecolor(colorb))
                            end
                        end
                        colora = colorb
                    end
                    for c in xmlcollected(spec,"/stop[@stop-opacity]") do
                        opacityb = c.at["stop-opacity"]
                        if not opacitya then
                            opacitya = opacityb
                        end
                    end
                    if opacitya then
                        opacs = { }
                        for i=1,lastns do
                            no = no + 1 ; opacs[no] = shade[i]
                        end
                        no = no + 1 ; opacs[no] = f_shade_colors(opacitya,opacityb)
                    end
                    return colora
                end
                --
                if not crap(spec) then
                    -- for now but maybe we need more
                 -- local id  = rawget(a,"href") or rawget(a,"xlink:href") -- better a rawget
                 -- local res = locate(id,c)
                    if res then
                        crap(res)
                    end
                end
                local transform
                if gt then
                    ns = ns + 1
                    transform = handletransformstring(gt)
                    shade[ns] = f_shade_transform(transform)
                    transform = f_shade_inverse(transform)
                    if opacitya then
                        no = no + 1
                        opacs[no] = shade[no]
                    end
                end
                shade = concat(shade,"\n    ")
                opacs = no > 0 and concat(opacs,"\n    ")
                return shade, opacs, transform
            end
        end
    end

    local function drawproperties(stroke,at,opacity)
        local p = at["stroke-width"]
        if p then
            p = f_pen(asnumber_r(p))
        end
        local d = at["stroke-dasharray"]
        if d == "none" then
            d = nil
        elseif d then
            local o = at["stroke-dashoffset"]
            if o and o ~= "none" then
                o = asnumber_r(o)
            else
                o = false
            end
            d = dashed(d,o)
        end
        local c = withcolor(stroke)
        local o = at["stroke-opacity"] or (opacity and at["opacity"])
        if o == "none" then
            o = nil
        elseif o == "transparent" then
            o = f_opacity(0)
        elseif o then
            o = asnumber_r(o)
            if o == ignoredopacity then
                o = nil
            elseif o then
                o = f_opacity(o)
            else
                o = nil
            end
        end
        return p, d, c, o
    end

    local s_opacity_start   <const> = "draw image ("
    local f_opacity_content         = formatters["setgroup currentpicture to boundingbox currentpicture withopacity %N;"]
    local s_opacity_stop    <const> = ") ;"

    local function sharedopacity(at)
        local o = at["opacity"]
        if o and o ~= "none" then
            o = asnumber_r(o)
            if o == ignoredopacity then
                return
            end
            if o then
                return s_opacity_start, f_opacity_content(o), s_opacity_stop
            end
        end
    end

    -- it looks like none and transparent are both used (mozilla examples)

    local function fillproperties(fill,at,opacity)
     -- local o = at["fill-opacity"] or (opacity and at["opacity"])
        local o = at["fill-opacity"] or (opacity and rawget(at,"opacity")) -- opacity not inherited as it can be on the group
        -- what a mess ... examples and suggestions are not consistent
        local o = at["fill-opacity"]
        if tonumber(o) == 1 then
            -- so kind of unset
            o = rawget(at,"opacity") or o
        end
        --
        local c   = nil
        local t   = nil
        local ops = nil
        if c ~= "none" then
            c, ops, t = gradient(fill)
            if ops then
                return c, ops, t
            end
            if not c then
                c = pattern(fill)
                if c then
                    if o and o ~= "none" then
                        o = asnumber_r(o)
                        if o ~= ignoredopacity then
                            return c, ops, t, f_opacity(o), "pattern"
                        end
                    end
                    return c, ops, t, false, "pattern"
                else
                    c = withcolor(fill)
                end
            end
        end
        if not o and fill == "transparent" then
            return nil, nil, nil, f_opacity(0), true
        elseif o and o ~= "none" then
            o = asnumber_r(o)
            if o == ignoredopacity then
                return c, nil, t
            end
            if o then
                return c, nil, t, f_opacity(o), (o == 1 and "invisible")
            end
        end
        return c, ops, t
    end

    local viewport do

        local s_viewport_start  <const> = "draw image ("
        local s_viewport_stop   <const> = ") ;"
        local f_viewport_shift          = formatters["currentpicture := currentpicture shifted (%N,%N);"]
        local f_viewport_scale          = formatters["currentpicture := currentpicture xysized (%N,%N);"]
        local f_viewport_clip           = formatters["clip currentpicture to (unitsquare xyscaled (%N,%N));"]

        viewport = function(x,y,w,h,noclip,scale)
            r = r + 1 ; result[r] = s_viewport_start
            return function()
                local okay = w ~= 0 and h ~= 0
                if okay and scale then
                    r = r + 1 ; result[r] = f_viewport_scale(w,h)
                end
                if x ~= 0 or y ~= 0 then
                    r = r + 1 ; result[r] = f_viewport_shift(-x,y)
                end
                if okay and not noclip then
                    r = r + 1 ; result[r] = f_viewport_clip(w,-h)
                end

                r = r + 1 ; result[r] = s_viewport_stop
            end
        end

    end

    -- maybe forget about defs and just always locate (and then backtrack
    -- over <g> if needed) .. so, only store after locating

    function handledefinitions(c)
        for c in xmlcollected(c,"defs/*") do
            local a = c.at
            if a then
                local id = rawget(a,"id")
                if id then
                    definitions["#"     .. id       ] = c
                    definitions["url(#" .. id .. ")"] = c
                end
            end
        end
        for c in xmlcollected(c,"(symbol|radialGradient|linearGradient)") do
            local id = rawget(c.at,"id")
            if id then
                definitions["#"     .. id       ] = c
                definitions["url(#" .. id .. ")"] = c
            end
        end
    end

 --  function handlers.defs(c)
 --     for c in xmlcollected(c,"/*") do
 --         local a = c.at
 --         if a then
 --             local id = rawget(a,"id")
 --             if id then
 --                 definitions["#"     .. id       ] = c
 --                 definitions["url(#" .. id .. ")"] = c
 --             end
 --         end
 --     end
 -- end

    -- lots of stuff todo: transform

    local uselevel = 0

    function handlers.use(c)
        local at  = c.at
        local id  = rawget(at,"href") or rawget(at,"xlink:href") -- better a rawget
        local res = locate(id,c)
        if res then
            uselevel = uselevel + 1
            local boffset, eoffset = handleoffset(at)
            local btransform, etransform, transform = handletransform(at)

            if boffset then
                r = r + 1 result[r] = boffset
            end

         -- local clippath  = at.clippath

            if btransform then
                r = r + 1 result[r] = btransform
            end

            local _transform = transform
            local _clippath  = clippath
            at["transform"] = false
         -- at["clip-path"] = false

            setmetatableindex(res.at,at)

            local tg = res.tg
         -- if usetags[tg] then
                process(res,".")
         -- else
         --     process(res,"/*")
         -- end

            at["transform"] = _transform
         -- at["clip-path"] = _clippath

            if etransform then
                r = r + 1 ; result[r] = etransform
            end

            if eoffset then
                r = r + 1 result[r] = eoffset
            end

            uselevel = uselevel - 1
        else
            report("use: unknown definition %a",id)
        end
    end

    local f_do_draw               = formatters['  draw (%s)']
    local f_do_fill               = formatters['  fill (%s)']
    local f_eo_fill               = formatters['  eofill (%s)']
    local s_clip_start    <const> = 'save p ; picture p ; p := image ('
    local f_clip_stop_c           = formatters[') ; clip p to (%s &&& cycle) %s ; draw p ;']
    local f_clip_stop_l           = formatters[') ; clip p to (%s &&& cycle) %s ; draw p ;']
    local f_clip_stop             = f_clip_stop_c
    local f_eoclip_stop_c         = formatters[') ; eoclip p to (%s &&& cycle) %s ; draw p ;']
    local f_eoclip_stop_l         = formatters[') ; eoclip p to (%s &&& cycle) %s ; draw p ;']
    local f_eoclip_stop           = f_eoclip_stop_c

    local f_shade_luminosity      = formatters['draw luminosityshade (\n  (%s &&& cycle) %s\n  ) (\n%s\n  ) (\n%s\n) ;']

    -- could be shared and then beginobject | endobject

    local function flushobject(object,at,c,o)
        local btransform, etransform = handletransform(at)
        local cpath, _, ctransform = handleclippath(at)

        if cpath then
            r = r + 1 ; result[r] = s_clip_start
        end

        if btransform then
            r = r + 1 ; result[r] = btransform
        end

        r = r + 1 ; result[r] = f_do_draw(object)

        if c then
            r = r + 1 ; result[r] = c
        end

        if o then
            r = r + 1 ; result[r] = o
        end

        if etransform then
            r = r + 1 ; result[r] = etransform
        end

        r = r + 1 ; result[r] = "  ;"

        if cpath then
            local f_done = cpath.evenodd
            if cpath.curve then
                f_done = f_done and f_eoclip_stop_c or f_clip_stop_c
            else
                f_done = f_done and f_eoclip_stop_l or f_clip_stop_l
            end
            r = r + 1 ; result[r] = f_done(cpath.path,ctransform or "")
        end

        if etransform then
            r = r + 1 ; result[r] = etransform
        end
    end

    do

        local flush

        local f_withlinecap    = formatters["    withlinecap %s"]
        local f_withlinejoin   = formatters["    withlinejoin %s"]
        local f_withmiterlimit = formatters["    withmiterlimit %s;"]

        local linecaps  = { butt  = "butt",    square = "squared", round = "rounded" }
        local linejoins = { miter = "mitered", bevel  = "beveled", round = "rounded" }

        local function lineproperties(at)
            local cap   = at["stroke-linecap"]
            local join  = at["stroke-linejoin"]
            local limit = at["stroke-miterlimit"]
            if cap then
                cap = linecaps[cap]
            end
            if join then
                join = linejoins[join]
            end
            if limit then
                limit = asnumber_r(limit)
            end
            return cap, join, limit
        end

        -- markers are a quite rediculous thing .. let's assume simple usage for now

        function handlers.marker(c)
         -- local a = c.at
         -- if a then
         --     local id = rawget(a,"id")
         --     if id then
         --         definitions["#"     .. id       ] = c
         --         definitions["url(#" .. id .. ")"] = c
         --     end
         -- end
        end

        -- kind of local svg ... so make a generic one
        --
        -- todo: combine more (offset+scale+rotation)

        local function makemarker(where,c,x1,y1,x2,y2,x3,y3,parentat)
            local at     = c.at
            local refx   = rawget(at,"refX")
            local refy   = rawget(at,"refY")
            local width  = rawget(at,"markerWidth")
            local height = rawget(at,"markerHeight")
            local units  = rawget(at,"markerUnits") -- no parentat["stroke-width"], bad for m4mbo
            local view   = rawget(at,"viewBox")
            local orient = rawget(at,"orient")
         -- local ratio  = rawget(at,"preserveAspectRatio")
            local units  = units and asnumber(units) or 1

            local angx   = 0
            local angy   = 0
            local angle  = 0

            if where == "beg" then
                if orient == "auto" then -- unchecked
                    -- no angle
                    angx = abs(x2 - x3)
                    angy = abs(y2 - y3)
                elseif orient == "auto-start-reverse" then -- checked
                    -- points to start
                    angx = -abs(x2 - x3)
                    angy = -abs(y2 - y3)
                elseif orient then -- unchecked
                    angle = asnumber_r(orient)
                end
            elseif where == "end" then
                -- funny standard .. bug turned feature?
                if orient == "auto" or orient == "auto-start-reverse" then
                    angx = abs(x1 - x2)
                    angy = abs(y1 - y2)
                elseif orient then -- unchecked
                    angle = asnumber_r(orient)
                end
            elseif orient then -- unchecked
                angle = asnumber_r(orient)
            end
            -- what wins: viewbox or w/h

            refx = asnumber_x(refx)
            refy = asnumber_y(refy)

            width  = (width  and asnumber_x(width)  or 3) * units
            height = (height and asnumber_y(height) or 3) * units

            local x = 0
            local y = 0
            local w = width
            local h = height

            -- kind of like the main svg

            r = r + 1 ; result[r] = s_offset_start

            local wrapupviewport

            -- todo : better viewbox code

            local xpct, ypct, rpct
            if view then
                x, y, w, h = handleviewbox(view)
            end

            if width ~= 0 then
                w = width
            end
            if height ~= 0 then
                h = height
            end

            if h then
                xpct           = percentage_x
                ypct           = percentage_y
                rpct           = percentage_r
                percentage_x   = w / 100
                percentage_y   = h / 100
                percentage_r   = (sqrt(w^2 + h^2) / sqrt(2)) / 100
                wrapupviewport = viewport(x,y,w,h,true,true) -- no clip
            end

            -- we can combine a lot here:

            local hasref = refx ~= 0 or refy ~= 0
            local hasrot = angx ~= 0 or angy ~= 0 or angle ~= 0

            local btransform, etransform, transform = handletransform(at)

            if btransform then
                r = r + 1 ; result[r] = btransform
            end

            if hasrot then
                r = r + 1 ; result[r] = s_rotation_start
            end

            if hasref then
                r = r + 1 ; result[r] = s_offset_start
            end

            local _transform = transform
            at["transform"] = false

            handlers.g(c)

            at["transform"] = _transform

            if hasref then
                r = r + 1 ; result[r] = f_offset_stop(-refx,refy)
            end

            if hasrot then
                if angle ~= 0 then
                    r = r + 1 ; result[r] = f_rotation_angle(angle)
                else
                    r = r + 1 ; result[r] = f_rotation_stop(angx,angy)
                end
            end

            if etransform then
                r = r + 1 ; result[r] = etransform
            end

            if h then
                percentage_x = xpct
                percentage_y = ypct
                percentage_r = rpct
                if wrapupviewport then
                    wrapupviewport()
                end
            end
            r = r + 1 ; result[r] = f_offset_stop(x2,y2)

        end

        -- do we need to metatable the attributes here?

        local function addmarkers(list,begmarker,midmarker,endmarker,at)
            local n = #list
            if n > 3 then
                if begmarker then
                    local m = locate(begmarker)
                    if m then
                        makemarker("beg",m,false,false,list[1],list[2],list[3],list[4],at)
                    end
                end
                if midmarker then
                    local m = locate(midmarker)
                    if m then
                        for i=3,n-2,2 do
                            makemarker("mid",m,list[i-2],list[i-1],list[i],list[i+1],list[i+2],list[i+3],at)
                        end
                    end
                end
                if endmarker then
                    local m = locate(endmarker)
                    if m then
                        makemarker("end",m,list[n-3],list[n-2],list[n-1],list[n],false,false,at)
                    end
                end
            else
                -- no line
            end
        end

        local function flush(shape,dofill,closed,at,list,begmarker,midmarker,endmarker)
            local fill   = dofill and (at["fill"] or "black")
            local stroke = at["stroke"] or "none"

            local btransform, etransform = handletransform(at)
            local cpath, _, ctransform = handleclippath(at)

            if cpath then
                r = r + 1 ; result[r] = s_clip_start
            end

            local has_stroke = stroke and stroke ~= "none"
            local has_fill   = fill and fill ~= "none"

            local bopacity, copacity, eopacity
            if has_stroke and has_fill then
                bopacity, copacity, eopacity = sharedopacity(at)
            end
            if copacity then
                r = r + 1 ; result[r] = bopacity
            end
            if has_fill and not closed then
                shape = shape .. "&&cycle"
            end
            if has_fill then
                local color, opacs, transform, opacity, option = fillproperties(fill,at,not has_stroke)
                local f_xx_fill = at["fill-rule"] == "evenodd" and f_eo_fill or f_do_fill
                if option ~= pattern and opacs then
                 -- btransform or "",
                 -- etransform or "",
                    r = r + 1 ; result[r] = f_shade_luminosity(shape,transform or "",opacs,color)
                else
                    if btransform then
                        r = r + 1 ; result[r] = btransform
                    end
                    if option == "pattern" then
                        r = r + 1 ; result[r] = f_d_draw(shape)
                    else
                        r = r + 1 ; result[r] = f_xx_fill(shape)
                        if transform then
                            r = r + 1 result[r] = transform
                        end
                    end
                    if color   then
                        r = r + 1 ; result[r] = color
                    end
                    if opacity then
                        r = r + 1 ; result[r] = opacity
                    end
                    r = r + 1 ; result[r] = etransform or "  ;"
                end
            end

            if has_stroke then
                local pen, dashing, color, opacity = drawproperties(stroke,at,not has_fill)
                local cap, join, limit = lineproperties(at)
                if btransform then
                    r = r + 1 ; result[r] = btransform
                end
                r = r + 1 ; result[r] = f_do_draw(shape)
                if pen     then r = r + 1 ; result[r] = pen     end
                if dashing then r = r + 1 ; result[r] = dashing end
                if color   then r = r + 1 ; result[r] = color   end
                if opacity then r = r + 1 ; result[r] = opacity end
                if cap     then r = r + 1 ; result[r] = f_withlinecap   (cap)   end
                if join    then r = r + 1 ; result[r] = f_withlinejoin  (join)  end
                if limit   then r = r + 1 ; result[r] = f_withmiterlimit(limit) end
                r = r + 1 ; result[r] = etransform or "  ;"
                --
                if list then
                    addmarkers(list,begmarker,midmarker,endmarker,at)
                end
            end
            if copacity then
                r = r + 1 ; result[r] = copacity
                r = r + 1 ; result[r] = eopacity
            end

            if cpath then
                r = r + 1 ; result[r] = (cpath.evenodd and f_eoclip_stop or f_clip_stop)(cpath.path,ctransform)
            end
        end

        local f_rectangle = formatters['unitsquare xyscaled (%N,%N) shifted (%N,%N)']
        local f_rounded   = formatters['roundedsquarexy(%N,%N,%N,%N) shifted (%N,%N)']
        local f_line      = formatters['((%N,%N)--(%N,%N))']
        local f_ellipse   = formatters['(fullcircle xyscaled (%N,%N) shifted (%N,%N))']
        local f_circle    = formatters['(fullcircle scaled %N shifted (%N,%N))']

        function handlers.line(c)
            local at = c.at
            local x1 = rawget(at,"x1")
            local y1 = rawget(at,"y1")
            local x2 = rawget(at,"x2")
            local y2 = rawget(at,"y2")

            x1 = x1 and asnumber_vx(x1) or 0
            y1 = y1 and asnumber_vy(y1) or 0
            x2 = x2 and asnumber_vx(x2) or 0
            y2 = y2 and asnumber_vy(y2) or 0

            flush(f_line(x1,y1,x2,y2),false,false,at)
        end

        function handlers.rect(c)
            local at     = c.at
            local width  = rawget(at,"width")
            local height = rawget(at,"height")
            local x      = rawget(at,"x")
            local y      = rawget(at,"y")
            local rx     = rawget(at,"rx")
            local ry     = rawget(at,"ry")

            width  = width  and asnumber_x(width)  or 0
            height = height and asnumber_y(height) or 0
            x      = x      and asnumber_vx(x) or 0
            y      = y      and asnumber_vy(y) or 0

            y      = y - height

            if rx then rx = asnumber_x(rx) end
            if ry then ry = asnumber_y(ry) end

            if rx or ry then
                if not rx then rx = ry end
                if not ry then ry = rx end
                flush(f_rounded(width,height,rx,ry,x,y),true,true,at)
            else
                flush(f_rectangle(width,height,x,y),true,true,at)
            end
        end

        function handlers.ellipse(c)
            local at = c.at
            local cx = rawget(at,"cx")
            local cy = rawget(at,"cy")
            local rx = rawget(at,"rx")
            local ry = rawget(at,"ry")

            cx = cx and asnumber_vx(cx) or 0
            cy = cy and asnumber_vy(cy) or 0
            rx = rx and asnumber_r (rx) or 0
            ry = ry and asnumber_r (ry) or 0

            flush(f_ellipse(2*rx,2*ry,cx,cy),true,true,at)
        end

        function handlers.circle(c)
            local at = c.at
            local cx = rawget(at,"cx")
            local cy = rawget(at,"cy")
            local r  = rawget(at,"r")

            cx = cx and asnumber_vx(cx) or 0
            cy = cy and asnumber_vy(cy) or 0
            r  = r  and asnumber_r (r)  or 0

            flush(f_circle(2*r,cx,cy),true,true,at)
        end

        local f_lineto_z  = formatters['(%N,%N)']
        local f_lineto_n  = formatters['--(%N,%N)']

        local p_pair     = p_optseparator * p_number_vx * p_optseparator * p_number_vy
        local p_open     = Cc("(")
        local p_close    = Carg(1) * P(true) / function(s) return s end
        local p_polyline = Cs(p_open * (p_pair / f_lineto_z) * (p_pair / f_lineto_n)^0 * p_close)
        local p_polypair = Ct(p_pair^0)

        local function poly(c,final)
            local at     = c.at
            local points = rawget(at,"points")
            if points then
                local path = lpegmatch(p_polyline,points,1,final)
                local list = nil
                local begmarker = rawget(at,"marker-start")
                local midmarker = rawget(at,"marker-mid")
                local endmarker = rawget(at,"marker-end")
                if begmarker or midmarker or endmarker then
                    list = lpegmatch(p_polypair,points)
                end
                -- check closed
                flush(path,true,false,at,list,begmarker,midmarker,endmarker)
            end
        end

        function handlers.polyline(c) poly(c,       ")") end
        function handlers.polygon (c) poly(c,"--cycle)") end

        function handlers.path(c)
            local at = c.at
            local d  = rawget(at,"d")
            if d then
                local shape, l = grabpath(d)
                local path     = shape.path
                if not path or #path == "" then
                    return
                end
                local fill   = at["fill"] or "black"
                local stroke = at["stroke"] or "none"
                local btransform, etransform = handletransform(at)
                local cpath, _, ctransform = handleclippath(at)
                if cpath then
                    r = r + 1 ; result[r] = s_clip_start
                end

                -- todo: image (nicer for transform too)

                if fill and fill ~= "none" then
                    local color, opacs, transform, opacity, option = fillproperties(fill,at)
                    local evenodd = at["fill-rule"] == "evenodd"
                    if not shape.closed then
                        path = path .. "&&cycle"
                    end
                    if opacs then
                     -- btransform or "",
                     -- etransform or "",
                        r = r + 1 ; result[r] = f_shade_luminosity(path,transform or "",opacs,color)
                    else
                        if btransform then
                            r = r + 1 ; result[r] = btransform
                        end
                        if option == "pattern" then
                            r = r + 1 result[r] = f_do_draw(path)
                        elseif evenodd then
                            r = r + 1 result[r] = f_eo_fill(path)
                        else
                            r = r + 1 result[r] = f_do_fill(path)
                        end
                        if color then
                            r = r + 1 ; result[r] = color
                        end
                        if opacity then
                            r = r + 1 ; result[r] = opacity
                        end
                        r = r + 1 ; result[r] = etransform or "  ;"
                    end
                end

                -- usflag in BT flags presentation, todo: in manual with a few
                -- more flags (greenland and canada crushing it or so)

local begmarker = rawget(at,"marker-start")
local midmarker = rawget(at,"marker-mid")
local endmarker = rawget(at,"marker-end")
local hasmarker = begmarker or midmarker or endmarker

                if (stroke and stroke ~= "none") then
                 -- local begmarker = rawget(at,"marker-start")
                 -- local midmarker = rawget(at,"marker-mid")
                 -- local endmarker = rawget(at,"marker-end")
                 -- local hasmarker = begmarker or midmarker or endmarker
                 -- if hasmarker then
                 --     list = grablist(l)
                 -- end
                    local pen, dashing, color, opacity = drawproperties(stroke,at)
                    local cap, join, limits = lineproperties(at)
                    if btransform then
                        r = r + 1 ; result[r] = btransform
                    end
                    r = r + 1 result[r] = f_do_draw(path)
                    if pen     then r = r + 1 ; result[r] = pen     end
                    if dashing then r = r + 1 ; result[r] = dashing end
                    if color   then r = r + 1 ; result[r] = color   end
                    if opacity then r = r + 1 ; result[r] = opacity end
                    if cap     then r = r + 1 ; result[r] = f_withlinecap   (cap)   end
                    if join    then r = r + 1 ; result[r] = f_withlinejoin  (join)  end
                    if limit   then r = r + 1 ; result[r] = f_withmiterlimit(limit) end
                    r = r + 1 result[r] = etransform or "  ;"
                 -- if list then
                 --     addmarkers(list,begmarker,midmarker,endmarker,at)
                 -- end
                end

if hasmarker then
    if btransform then
        r = r + 1 ; result[r] = btransform
    end
    list = grablist(l)
    addmarkers(list,begmarker,midmarker,endmarker,at)
    r = r + 1 result[r] = etransform or "  ;"
end

                if cpath then
                    r = r + 1 ; result[r] = f_clip_stop(cpath.path,ctransform)
                end
            end
        end

    end

    -- kind of special

    do

        -- some day:
        --
        -- specification = identifiers.jpg(data."string")
        -- specification.data = data
        -- inclusion takes from data
        -- specification.data = false

     -- local f_image = formatters[ [[figure("%s") xysized (%N,%N) shifted (%N,%N)]] ]
        local f_image = formatters[ [[svgembeddedfigure(%i) xysized (%N,%N) shifted (%N,%N)]] ]

     -- local nofimages = 0

        function handlers.image(c)
            local at = c.at
            local im = rawget(at,"xlink:href")
            if im then
                local kind, data = match(im,"^data:image/([a-z]+);base64,(.*)$")
                if kind == "png" then
                    -- ok
                elseif kind == "jpeg" then
                    kind = "jpg"
                else
                    kind = false
                end
                if kind and data then
                    local w  = rawget(at,"width")
                    local h  = rawget(at,"height")
                    local x  = rawget(at,"x")
                    local y  = rawget(at,"y")
                    w = w and asnumber_x(w)
                    h = h and asnumber_y(h)
                    x = x and asnumber_vx(x) or 0
                    y = y and asnumber_vy(y) or 0
                    local data = basexx.decode64(data)
                 -- local name  = "temp-svg-image-" .. nofimages .. "." .. kind
                    local index = images.storedata("svg", {
                        kind = kind,
                        data = data,
                        info = graphics.identifiers[kind](data,"string"),
                    })
                 -- io.savedata(name,data)
                    if not w or not h then
                        if info then
                            -- todo: keep aspect ratio attribute
                            local xsize = info.xsize
                            local ysize = info.ysize
                            if not w then
                                if not h then
                                    w = xsize
                                    h = ysize
                                else
                                    w = (h / ysize) * xsize
                                end
                            else
                                h = (w / xsize) * ysize
                            end
                        end
                    end
                    -- safeguard:
                    if not w then w = h or 1 end
                    if not h then h = w or 1 end
                 -- luatex.registertempfile(name)
                 -- flushobject(f_image(name,w,h,x,y - h),at)
                    flushobject(f_image(index,w,h,x,y - h),at)
                else
                    -- nothing done
                end
            end
        end

    end

    -- these transform: g a text svg symbol

    do

        function handlers.a(c)
            process(c,"/*")
        end

        function handlers.g(c) -- much like flushobject so better split and share
            local at = c.at

            local btransform, etransform, transform = handletransform(at)
            local cpath, clippath, ctransform = handleclippath(at)
            local bopacity, copacity, eopacity = sharedopacity(at)
            if copacity then
                r = r + 1 ; result[r] = bopacity
            end

            if cpath then
                r = r + 1 ; result[r] = s_clip_start
            end

            if btransform then
                r = r + 1 result[r] = btransform
            end
            local _transform = transform
            local _clippath  = clippath
            at["transform"] = false
            at["clip-path"] = false

            process(c,"/!(defs|symbol)") -- /*

            at["transform"] = _transform
            at["clip-path"] = _clippath

            if etransform then -- maybe move after cpath
                r = r + 1 ; result[r] = etransform
            end
            if cpath then
                local f_done = cpath.evenodd
                if cpath.curve then
                    f_done = f_done and f_eoclip_stop_c or f_clip_stop_c
                else
                    f_done = f_done and f_eoclip_stop_l or f_clip_stop_l
                end
                r = r + 1 ; result[r] = f_done(cpath.path,ctransform or "")
            end
            if copacity then
                r = r + 1 ; result[r] = copacity
                r = r + 1 ; result[r] = eopacity
            end
        end

        -- this will never really work out
        --
        -- todo: register text in lua in mapping with id, then draw mapping unless overloaded
        --       using lmt_svglabel with family,style,weight,size,id passed

        -- nested tspans are messy: they can have displacements but in inkscape we also
        -- see x and y (inner and outer element)

        -- The size is a bit of an issue. I assume that the specified size relates to the
        -- designsize but we want to be able to use other fonts.

        -- a mix of text and spans and possibly wrap (where xy is to be ignored) ... bah ...
        -- it's fuzzy when we have a span with positions mixed with text ... basically that
        -- is a box and we can assume that an editor then has all positioned

        do

            local s_start     <const> = "\\svgstart "
            local s_stop      <const> = "\\svgstop "
            local f_set               = formatters["\\svgset{%N}{%N}"] -- we need a period
            local f_color_c           = formatters["\\svgcolorc{%.3N}{%.3N}{%.3N}{"]
            local f_color_o           = formatters["\\svgcoloro{%.3N}{"]
            local f_color_b           = formatters["\\svgcolorb{%.3N}{%.3N}{%.3N}{%.3N}{"]
            local f_poscode           = formatters["\\svgpcode{%N}{%N}{%s}"]
            local f_poschar           = formatters["\\svgpchar{%N}{%N}{%s}"]
            local f_posspace          = formatters["\\svgpspace{%N}{%N}"]
            local f_code              = formatters["\\svgcode{%s}"]
            local f_char              = formatters["\\svgchar{%s}"]
            local s_space     <const> = "\\svgspace "
            local f_size              = formatters["\\svgsize{%0.6f}"]             -- we need a period
            local f_font              = formatters["\\svgfont{%s}{%s}{%s}"]
            local f_hashed            = formatters["\\svghashed{%s}"]

            ----- p_texescape = lpegpatterns.texescape

            local anchors = {
                ["start"]  = "drt",
                ["end"]    = "dlft",
                ["middle"] = "d",
            }

            -- we can now just use the lmt maptext feature

            local f_text_normal_svg   = formatters['(onetimetextext.%s("%s") shifted (%N,%N))']
            local f_text_simple_svg   = formatters['onetimetextext.%s("%s")']
            local f_mapped_normal_svg = formatters['(svgtext("%s") shifted (%N,%N))']
            local f_mapped_simple_svg = formatters['svgtext("%s")']

            local cssfamily  = css.family
            local cssstyle   = css.style
            local cssweight  = css.weight
            local csssize    = css.size

            local usedfonts  = setmetatableindex(function(t,k)
                local v = setmetatableindex("table")
                t[k] = v
                return v
            end)

            -- For now as I need it for my (some 1500) test files.

            local function checkedfamily(name)
                if find(name,"^.-verdana.-$") then
                    name = "verdana"
                end
                return name
            end

            -- todo: only escape some chars and handle space

         -- An arbitrary mix of text and spans with x/y is asking for troubles. The fact that the
         -- description in the (proposed) standard is so complex indicates this (its also looks
         -- like reveng application specs and doesn't aim at simplicity. Basically we have two
         -- cases: positioned lines and words and such (text & span with xy), or just stripes of
         -- text and span. Free flow automatically broken into lines text is kind of strange in
         -- svg and the fact that glyph placement is dropped is both an indication that svg lost
         -- part of its purpose and probably also that it never really was a standard (although
         -- maybe today standards are just short term specifications. Who knows.

         -- text with spans, all with x/y
         -- text mixed with spans, no xy in inner elements
         --
         -- the spec says that nested x/y are absolute

            local defaultsize = 10

            local sensitive = { -- todo: characters.sensitive
                ["#"] = true,
                ["$"] = true,
                ["%"] = true,
                ["&"] = true,
               ["\\"] = true,
                ["{"] = true,
                ["|"] = true,
                ["}"] = true,
                ["~"] = true,
            }

            -- messy: in nested spans (they happen) the x/y are not accumulated

            local function validdelta(usedscale,d)
                if d then
                    local value, unit = match(d,"^([%A]-)(%a+)")
                    value = tonumber(value) or 0
                    if not unit then
                        return value .. "bp"
                    elseif unit == "ex" or unit == "em" then
                        return (usedscale * value) .. unit
                    else
                        return value .. "bp"
                    end
                else
                    return "0bp"
                end
            end

            local cleanfontname = fonts.names.cleanname

            local x_family = false
            local x_weight = false
            local x_style  = false

            local function collect(parent,t,c,x,y,size,scale,family,tx,ty,tdx,tdy)
                if c.special then
                    return nil
                end
                local dt           = c.dt
                local nt           = #dt
                local at           = c.at
                local tg           = c.tg
                local ax           = rawget(at,"x")
                local ay           = rawget(at,"y")
                local v_opacity    = tonumber(at["fill-opacity"])
                local v_fill       = at["fill"]
                local v_family     = at["font-family"]
                local v_style      = at["font-style"]
                local v_weight     = at["font-weight"]
                local v_size       = at["font-size"]
                local v_lineheight = at["line-height"]
                --
                ax = ax and asnumber_vx(ax) or x
                ay = ay and asnumber_vy(ay) or y
                --
                if v_family then v_family = cssfamily(v_family) end
                if v_style  then v_style  = cssstyle (v_style)  end
                if v_weight then v_weight = cssweight(v_weight) end
                if v_size   then v_size   = csssize  (v_size,factors,size/100) or tonumber(v_size) end
                --
                if not v_family then v_family = family   end
                if not v_weight then v_weight = "normal" end
                if not v_style  then v_style  = "normal" end
                --
                if v_family then
                    v_family = cleanfontname(v_family)
                    v_family = checkedfamily(v_family)
                end
                --
                usedfonts[v_family][v_weight][v_style] = true
                --
                local lh = v_lineheight and asnumber_vx(v_lineheight) or false
                --
                ax = ax - x
                ay = ay - y
                --
                local usedsize  = v_size or defaultsize
                local usedscale = usedsize / defaultsize
                --
                -- todo: rotate            : list of numbers
                -- todo: lengthAdjust      : spacing|spacingAndGlyphs
                -- todo: textLength        : scale to width
                -- toto: font-size-adjust
                -- toto: font-stretch
                -- letter-spacing
                -- word-spacing
                -- writing-mode:lr-tb
                --
                local newfont = v_family ~= x_family or v_weight ~= x_weight or v_style ~= x_style
                if newfont then
                    x_family = v_family
                    x_weight = v_weight
                    x_style  = v_style
                    t[#t+1] = f_font(v_family,v_weight,v_style)
                    t[#t+1] = "{"
                end
                t[#t+1] = f_size(usedscale)
                t[#t+1] = "{"
                --
                if trace_fonts then
                    -- we can hash and keep it when no change
                    report("element        : %s",c.tg)
                    report("  font family  : %s",v_family)
                    report("  font weight  : %s",v_weight)
                    report("  font style   : %s",v_style)
                    report("  parent size  : %s",size)
                 -- report("  parent scale : %s",scale)
                    report("  used size    : %s",v_size or defaultsize)
                end
                --
                local ecolored = v_fill    ~= ""             and v_fill    or false
                local opacity  = v_opacity ~= ignoredopacity and v_opacity or false
                --
                -- todo cmyk
                --
                if ecolored then
                    local r, g, b = colorcomponents(v_fill)
                    if r and g and b then
                        if opacity then
                            t[#t+1] = f_color_b(r,g,b,opacity)
                        else
                            t[#t+1] = f_color_c(r,g,b)
                        end
                    elseif opacity then
                        t[#t+1] = f_color_o(opacity)
                    else
                        ecolored = false
                    end
                elseif opacity then
                    t[#t+1] = f_color_o(opacity)
                end
                --
                local hasa = ax ~= 0 or ay ~= 0
                if hasa then
                    -- we abuse the fact that flushing layers can be nested
                    t[#t+1] = f_set(ax or 0,ay or 0)
                    t[#t+1] = "{"
                end
                for i=1,nt do
                    local di = dt[i]
                    if type(di) == "table" then
                        -- when x or y then absolute else inline
                        if #di.dt > 0 then
                            collect(tg,t,di,x,y,usedsize,usedscale,v_family)
                        end
                    else
                        -- check for preserve
                        if i == 1 then
                            di = gsub(di,"^%s+","")
                        end
                        if i == nt then
                            di = gsub(di,"%s+$","")
                        end
                        local chars = utfsplit(di)
                        if svghash then
                            -- dx dy
                            di = f_hashed(svghash[di])
                        else
                            if tx or ty or tdx or tdy then
                                local txi, tyi, tdxi, tdyi
                                for i=1,#chars do
                                    txi  = tx  and (tx [i] or txi )
                                    tyi  = ty  and (ty [i] or tyi )
                                    tdxi = tdx and (tdx[i] or tdxi) or 0
                                    tdyi = tdy and (tdy[i] or tdyi) or 0
                                    local dx = (txi and (txi - x) or 0) + tdxi
                                    local dy = (tyi and (tyi - y) or 0) + tdyi
                                    local ci = chars[i]
                                    if ci == " " then
                                        chars[i] = f_posspace(dx, dy)
                                    elseif sensitive[ci] then
                                        chars[i] = f_poscode(dx, dy, utfbyte(ci))
                                    else
                                        chars[i] = f_poschar(dx, dy, ci)
                                    end
                                end
                                di = "{" .. concat(chars) .. "}"
                                t[#t+1] = di
                            else
                                -- this needs to be texescaped ! and even quotes and newlines
                                -- or we could register it but that's a bit tricky as we nest
                                -- and don't know what we can expect here
                             -- di = lpegmatch(p_texescape,di) or di
                                for i=1,#chars do
                                    local ci = chars[i]
                                    if ci == " " then
                                        chars[i] = s_space
                                    elseif sensitive[ci] then
                                        chars[i] = f_code(utfbyte(ci))
                                    else
                                        chars[i] = f_char(ci)
                                     -- chars[i] = ci
                                    end
                                end
                                di = concat(chars)
                                t[#t+1] = di
                            end
                        end
                    end
                end
                if hasa then
                    if t[#t] == "{" then
                        t[#t] = nil
                        t[#t] = nil
                    else
                        t[#t+1] = "}"
                    end
                end
                --
                if opacity or ecolored then
                    t[#t+1] = "}"
                end
                --
                t[#t+1] = "}"
                --
                if newfont then
                    t[#t+1] = "}"
                end
                --
                return t
            end

            -- case 1: just text, maybe with spans
            -- case 2: only positioned spans
            -- case 3: just text, seen as label

            local textlevel = 0

            function handlers.text(c)
                if textlevel == 0 then
                    x_family = v_family
                    x_weight = v_weight
                    x_style  = v_style
                end
                --
                textlevel = textlevel + 1
                -- analyze
                local only = fullstrip(xmltextonly(c))
                local at   = c.at
                local x    = rawget(at,"x")
                local y    = rawget(at,"y")

                local dx   = rawget(at,"dx")
                local dy   = rawget(at,"dy")

                local tx   = asnumber_vx_t(x)
                local ty   = asnumber_vy_t(y)

                local tdx  = asnumber_vx_t(dx)
                local tdy  = asnumber_vy_t(dy)

                x  = tx[1]  or 0 -- catch bad x/y spec
                y  = ty[1]  or 0 -- catch bad x/y spec

                dx = tdx[1] or 0 -- catch bad x/y spec
                dy = tdy[1] or 0 -- catch bad x/y spec

                local v_fill = at["fill"]
                if not v_fill or v_fill == "none" then
                    v_fill = "black"
                end
                local color, opacs, transform, opacity, option = fillproperties(v_fill,at)
                local anchor = anchors[at["text-anchor"] or "start"] or "drt"
                local remap = metapost.remappedtext(only)
                -- x = x + dx
                -- y = y + dy
                if remap then
                    if x == 0 and y == 0 then
                        only = f_mapped_simple_svg(remap.index)
                    else
                        only = f_mapped_normal_svg(remap.index,x,y)
                    end
                    flushobject(only,at,color,opacity)
                    if trace_text then
                        report("text: %s",only)
                    end
                elseif option == "invisible" then
                    if trace_text then
                        report("invisible text: %s",only)
                    end
                else
                    local scale  = 1
                    local textid = 0
                    local result = { }
                    local nx     = #tx
                    local ny     = #ty
                    local ndx    = #tdx
                    local ndy    = #tdy
                    --
                    local t = { }
                    t[#t+1] = s_start
                    if nx > 1 or ny > 1 or ndx > 1 or ndy > 1 then
                        collect(tg,t,c,x,y,defaultsize,1,"serif",tx,ty,tdx,tdy)
                    else
                        collect(tg,t,c,x,y,defaultsize,1,"serif")
                    end
                    t[#t+1] = s_stop
                    t = concat(t)
                    if x == 0 and y == 0 then
                        t = f_text_simple_svg(anchor,t)
                    else -- dx dy
                        t = f_text_normal_svg(anchor,t,x,y)
                    end
                 -- flushobject(t,at,color,opacity) -- otherwise mixup with transparency
                    flushobject(t,at,false,false)
                    if trace_text then
                        report("text: %s",result)
                    end
                end
                --
                textlevel = textlevel - 1
            end

            function metapost.reportsvgfonts()
                for family, weights in sortedhash(usedfonts) do
                    for weight, styles in sortedhash(weights) do
                        for style in sortedhash(styles) do
                            report("used font: %s-%s-%s",family,weight,style)
                        end
                    end
                end
            end

            statistics.register("used svg fonts",function()
                if next(usedfonts) then
                    -- also in log file
                    logs.startfilelogging(report,"used svg fonts")
                    local t = { }
                    for family, weights in sortedhash(usedfonts) do
                        for weight, styles in sortedhash(weights) do
                            for style in sortedhash(styles) do
                                report("%s-%s-%s",family,weight,style)
                                t[#t+1] = formatters["%s-%s-%s"](family,weight,style)
                            end
                        end
                    end
                    logs.stopfilelogging()
                    return concat(t," ")
                end
            end)

        end

        function handlers.svg(c,x,y,w,h,noclip,notransform,normalize)
            local at = c.at

            local wrapupviewport
            local bhacked
            local ehacked
            local wd = w
         -- local ex, em
            local xpct, ypct, rpct

            local btransform, etransform, transform = handletransform(at)

            if trace then
                report("view: %s, xpct %N, ypct %N","before",percentage_x,percentage_y)
            end

            local viewbox = at.viewBox

            if viewbox then
                x, y, w, h = handleviewbox(viewbox)
                if trace then
                    report("viewbox: x %N, y %N, width %N, height %N",x,y,w,h)
                end
            end
            if not w or not h or w == 0 or h == 0 then
                noclip = true
            end
            if h then
                --
             -- em = factors["em"]
             -- ex = factors["ex"]
             -- factors["em"] = em
             -- factors["ex"] = ex
                --
                xpct = percentage_x
                ypct = percentage_y
                rpct = percentage_r
                percentage_x = w / 100
                percentage_y = h / 100
                percentage_r = (sqrt(w^2 + h^2) / sqrt(2)) / 100
                if trace then
                    report("view: %s, xpct %N, ypct %N","inside",percentage_x,percentage_y)
                end
                wrapupviewport = viewport(x,y,w,h,noclip)
            end
            -- todo: combine transform and offset here

            -- some fonts need this (bad transforms + viewbox)
            if v and normalize and w and wd and w ~= wd and w > 0 and wd > 0 then
                bhacked = s_wrapped_start
                ehacked = f_wrapped_stop(y or 0,wd/w)
            end
            if btransform then
                r = r + 1 ; result[r] = btransform
            end
            if bhacked then
                r = r + 1 ; result[r] = bhacked
            end
            local boffset, eoffset = handleoffset(at)
            if boffset then
                r = r + 1 result[r] = boffset
            end

            at["transform"] = false
            at["viewBox"]   = false

            process(c,"/!(defs|symbol)")

            at["transform"] = transform
            at["viewBox"]   = viewbox

            if eoffset then
                r = r + 1 result[r] = eoffset
            end
            if ehacked then
                r = r + 1 ; result[r] = ehacked
            end
            if etransform then
                r = r + 1 ; result[r] = etransform
            end
            if h then
                --
             -- factors["em"] = em
             -- factors["ex"] = ex
                --
                percentage_x = xpct
                percentage_y = ypct
                percentage_r = rpct
                if wrapupviewport then
                    wrapupviewport()
                end
            end
            if trace then
                report("view: %s, xpct %N, ypct %N","after",percentage_x,percentage_y)
            end
        end

    end

    process = function(x,p)
        for c in xmlcollected(x,p) do
            local tg = c.tg
            local h  = handlers[c.tg]
            if h then
                h(c)
            end
        end
    end

    -- For huge inefficient files there can be lots of garbage to collect so
    -- maybe we should run the collector when a file is larger than say 50K.

    metapost.svgcachefile = false

    function metapost.svgtomp(specification,pattern,notransform,normalize)
        local mps = ""
        local svg = specification.data
        images.resetstore("svg")
        if type(svg) == "string" then
            svg = xmlconvert(svg)
        end
        if svg then
            local c = xmlfirst(svg,pattern or "/svg")
            if c then
                root        = svg
                result      = { }
                r           = 0
                definitions = { }
                tagstyles   = { }
                classstyles = { }
                colormap    = specification.colormap
                usedcolors  = trace_colors and setmetatableindex("number") or false
                for s in xmlcollected(c,"style") do -- can also be in a def, so let's play safe
                    handlestyle(c)
                end
                handlechains(c)
                xmlinheritattributes(c) -- put this in handlechains
                handledefinitions(c)
                handlers.svg (
                    c,
                    specification.x,
                    specification.y,
                    specification.width,
                    specification.height,
                    specification.noclip,
                    notransform,
                    normalize,
                    specification.remap
                )
                if trace_result == "file" then
                    io.savedata(
                        tex.jobname .. "-svg-to-mp.tex",
                        "\\startMPpage[instance=doublefun]\n" .. concat(result,"\n") .. "\n\\stopMPpage\n"
                    )
                elseif trace_result then
                    report("result graphic:\n    %\n    t",result)
                end
                if usedcolors and next(usedcolors) then
                    report("graphic %a uses colors: %s",specification.id or "unknown",table.sequenced(usedcolors))
                end
                mps         = concat(result," ")
                if metapost.svgcachefile then
                    io.savedata(metapost.svgcachefile,mps)
                    metapost.svgcachefile = false
                end
                root        = false
                result      = false
                r           = false
                definitions = false
                tagstyles   = false
                classstyles = false
                colormap    = false
            else
                report("missing svg root element")
            end
        else
            report("bad svg blob")
        end
        return mps
    end

end

-- These helpers might move to their own module .. some day ... also they will become
-- a bit more efficient, because we now go to mp and back which is kind of redundant,
-- but for now it will do.

do

    local bpfactor <const> = number.dimenfactors.bp

    -- We could share with graphic inclusion but svg is kind of out of scope and
    -- we also use this without externalfigure.

    local validdir = setmetatableindex(function(t,k)
        local v = isdir(k) or mkdir(k)
        t[k] = v
        return v
    end)

    function metapost.includesvgfile(filename,offset,cache) -- offset in sp
        local fullname = resolvers.findbinfile(filename)
        if isfile(fullname) then
            -- Caching, for Wolfgang.
            local cachename = false
            local reuse     = false
            if cache and cache ~= "" then
                cachename = resolvers.cleanpath(file.nameonly(fullname)) .. "-svg.mps"
                cache = resolvers.cleanpath(cache)
                if cache ~= "" and cache ~= interfaces.variables.yes and validdir[cache] then
                    cachename = file.join(cache,cachename)
                else
                    cachename = "m_k_i_v_" .. cachename
                end
                local old = modification(cachename)
                local new = modification(filename)
                reuse = old and new and new < old
            end
            context.startMPcode("doublefun")
            if reuse then
                if trace then
                    report("reusing already converted %a",filename)
                end
                context('input "%s" ;',cachename)
             -- context(io.loaddata(cachename))
            else
                metapost.svgcachefile = cachename
                context('draw lmt_svg [ filename = "%s", offset = %N ] ;',filename,(offset or 0)*bpfactor)
                if cachename and trace then
                    report("caching converted %a",filename)
                end
            end
            context.stopMPcode()
        end
    end

    function metapost.includesvgbuffer(name,offset,cache) -- offset in sp
        context.startMPcode("doublefun")
            context('draw lmt_svg [ buffer = "%s", offset = %N ] ;',name or "",(offset or 0)*bpfactor)
        context.stopMPcode()
    end

    interfaces.implement {
        name      = "includesvgfile",
        actions   = metapost.includesvgfile,
        arguments = { "string", "dimension", "string" },
    }

    interfaces.implement {
        name      = "includesvgbuffer",
        actions   = metapost.includesvgbuffer,
        arguments = { "string", "dimension", "string" },
    }

    function metapost.showsvgpage(data)
        local dd = data.data
        if not dd then
            local filename = data.filename
            local fullname = filename and resolvers.findbinfile(filename)
            dd = fullname and table.load(fullname)
        end
        if type(dd) == "table" then
            local comment = data.comment
            local offset  = data.pageoffset
            local index   = data.index
            local first   = math.max(index or 1,1)
            local last    = math.min(index or #dd,#dd)
            for i=first,last do
                local d = setmetatableindex( {
                    data       = dd[i],
                    comment    = comment and i or false,
                    pageoffset = offset or nil,
                }, data)
                metapost.showsvgpage(d)
            end
        elseif data.method == "code" then
            context.startMPcode(doublefun)
                context(metapost.svgtomp(data))
            context.stopMPcode()
        else
            context.startMPpage { instance = "doublefun", offset = data.pageoffset or nil }
                context(metapost.svgtomp(data))
                local comment = data.comment
                if comment then
                    context("draw boundingbox currentpicture withcolor .6red ;")
                    context('draw textext.bot("\\strut\\tttf %s") ysized (10pt) shifted center bottomboundary currentpicture ;',comment)
                end
            context.stopMPpage()
        end
    end

    function metapost.typesvgpage(data)
        local dd = data.data
        if not dd then
            local filename = data.filename
            local fullname = filename and resolvers.findbinfile(filename)
            dd = fullname and table.load(fullname)
        end
        if type(dd) == "table" then
            local index = data.index
            if index and index > 0 and index <= #dd then
                data = dd[index]
            else
                data = nil
            end
        end
        if type(data) == "string" and data ~= "" then
            buffers.assign("svgpage",data)
            context.typebuffer ({ "svgpage" }, { option = "XML", strip = "yes" })
        end
    end

    function metapost.svgtopdf(data,...)
        local mps = metapost.svgtomp(data,...)
        if mps then
            -- todo: special instance, only basics needed
            local pdf = metapost.simple("metafun",mps,true,false,"svg")
            if pdf then
                return pdf
            else
                -- message
            end
        else
            -- message
        end
    end

end

do

    local runner = sandbox.registerrunner {
        name     = "otfsvg2pdf",
        program  = "context",
        template = "--batchmode --purgeall --runs=2 %filename%",
        reporter = report_svg,
    }

    -- By using an independent pdf file instead of pdf streams we can use resources and still
    -- cache. This is the old method updated. Maybe a future version will just do this runtime
    -- but for now this is the most efficient method.

    local decompress = gzip.decompress
    local compress   = gzip.compress

    function metapost.svgshapestopdf(svgshapes,pdftarget,report_svg)
        local texname   = "temp-otf-svg-to-pdf.tex"
        local pdfname   = "temp-otf-svg-to-pdf.pdf"
        local tucname   = "temp-otf-svg-to-pdf.tuc"
        local nofshapes = #svgshapes
        local pdfpages  = { filename = pdftarget }
        local pdfpage   = 0
        local t         = { }
        local n         = 0
        --
        os.remove(texname)
        os.remove(pdfname)
        os.remove(tucname)
        --
        if report_svg then
            report_svg("processing %i svg containers",nofshapes)
            statistics.starttiming(pdfpages)
        end
        --
        -- can be option:
        --
     -- n = n + 1 ; t[n] = "\\nopdfcompression"
        --
        n = n + 1 ; t[n] = "\\starttext"
        n = n + 1 ; t[n] = "\\setupMPpage[alternative=offset,instance=doublefun]"
        --
        for i=1,nofshapes do
            local entry = svgshapes[i]
            local data  = entry.data
            if decompress then
                data = decompress(data) or data
            end
            local specification = {
                data   = xmlconvert(data),
                x      = 0,
                y      = 1000,
                width  = 1000,
                height = 1000,
                noclip = true,
            }
            for index=entry.first,entry.last do
                if not pdfpages[index] then
                    pdfpage = pdfpage + 1
                    pdfpages[index] = pdfpage
                    local pattern = "/svg[@id='glyph" .. index .. "']"
                    n = n + 1 ; t[n] = "\\startMPpage"
                    n = n + 1 ; t[n] = metapost.svgtomp(specification,pattern,true,true) or ""
                    n = n + 1 ; t[n] = "\\stopMPpage"
                end
            end
        end
        n = n + 1 ; t[n] = "\\stoptext"
        io.savedata(texname,concat(t,"\n"))
        runner { filename = texname }
        os.remove(pdftarget)
        file.copy(pdfname,pdftarget)
        if report_svg then
            statistics.stoptiming(pdfpages)
            report_svg("svg conversion time %s",statistics.elapsedseconds(pdfpages))
        end
        os.remove(texname)
        os.remove(pdfname)
        os.remove(tucname)
        return pdfpages
    end

    function metapost.svgshapestomp(svgshapes,report_svg)
        local nofshapes = #svgshapes
        local mpshapes = { }
        if report_svg then
            report_svg("processing %i svg containers",nofshapes)
            statistics.starttiming(mpshapes)
        end
        for i=1,nofshapes do
            local entry = svgshapes[i]
            local data  = entry.data
            if decompress then
                data = decompress(data) or data
            end
            local specification = {
                data   = xmlconvert(data),
                x      = 0,
                y      = 1000,
                width  = 1000,
                height = 1000,
                noclip = true,
            }
            for index=entry.first,entry.last do
                if not mpshapes[index] then
                    local pattern = "/svg[@id='glyph" .. index .. "']"
                    local mpcode  = metapost.svgtomp(specification,pattern,true,true) or ""
                    if mpcode ~= "" and compress then
                        mpcode = compress(mpcode) or mpcode
                    end
                    mpshapes[index] = mpcode
                end
            end
        end
        if report_svg then
            statistics.stoptiming(mpshapes)
            report_svg("svg conversion time %s",statistics.elapsedseconds(mpshapes))
        end
        return mpshapes
    end

    function metapost.svgglyphtomp(fontname,unicode)
        if fontname and unicode then
            local id = fonts.definers.internal { name = fontname }
            if id then
                local tfmdata = fonts.hashes.identifiers[id]
                if tfmdata then
                    local properties = tfmdata.properties
                    local svg        = properties.svg
                    local hash       = svg and svg.hash
                    local timestamp  = svg and svg.timestamp
                    if hash then
                        local svgfile   = containers.read(fonts.handlers.otf.svgcache,hash)
                        local svgshapes = svgfile and svgfile.svgshapes
                        if svgshapes then
                            if type(unicode) == "string" then
                                unicode = utfbyte(unicode)
                            end
                            local chardata = tfmdata.characters[unicode]
                            local index    = chardata and chardata.index
                            if index then
                                for i=1,#svgshapes do
                                    local entry = svgshapes[i]
                                    if index >= entry.first and index <= entry.last then
                                        local data  = entry.data
                                        if data then
                                            local root = xml.convert(gzip.decompress(data) or data)
                                            return metapost.svgtomp (
                                                {
                                                    data   = root,
                                                    x      = 0,
                                                    y      = 1000,
                                                    width  = 1000,
                                                    height = 1000,
                                                    noclip = true,
                                                },
                                                "/svg[@id='glyph" .. index .. "']",
                                                true,
                                                true
                                            )
                                        end
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end
    end

end
