diff options
Diffstat (limited to 'tex')
21 files changed, 4868 insertions, 100 deletions
| diff --git a/tex/context/base/mkii/cont-new.mkii b/tex/context/base/mkii/cont-new.mkii index 81663319d..02a4e9ee1 100644 --- a/tex/context/base/mkii/cont-new.mkii +++ b/tex/context/base/mkii/cont-new.mkii @@ -11,7 +11,7 @@  %C therefore copyrighted by \PRAGMA. See mreadme.pdf for  %C details. -\newcontextversion{2020.12.08 18:41} +\newcontextversion{2020.12.09 10:48}  %D This file is loaded at runtime, thereby providing an  %D excellent place for hacks, patches, extensions and new diff --git a/tex/context/base/mkii/context.mkii b/tex/context/base/mkii/context.mkii index 4ca1964c8..64f3f8425 100644 --- a/tex/context/base/mkii/context.mkii +++ b/tex/context/base/mkii/context.mkii @@ -20,7 +20,7 @@  %D your styles an modules.  \edef\contextformat {\jobname} -\edef\contextversion{2020.12.08 18:41} +\edef\contextversion{2020.12.09 10:48}  %D For those who want to use this: diff --git a/tex/context/base/mkiv/cont-new.mkiv b/tex/context/base/mkiv/cont-new.mkiv index d2d71822b..47295c850 100644 --- a/tex/context/base/mkiv/cont-new.mkiv +++ b/tex/context/base/mkiv/cont-new.mkiv @@ -13,7 +13,7 @@  % \normalend % uncomment this to get the real base runtime -\newcontextversion{2020.12.08 18:41} +\newcontextversion{2020.12.09 10:48}  %D This file is loaded at runtime, thereby providing an excellent place for hacks,  %D patches, extensions and new features. There can be local overloads in cont-loc diff --git a/tex/context/base/mkiv/context.mkiv b/tex/context/base/mkiv/context.mkiv index 40329734b..4ff38fa11 100644 --- a/tex/context/base/mkiv/context.mkiv +++ b/tex/context/base/mkiv/context.mkiv @@ -45,7 +45,7 @@  %D {YYYY.MM.DD HH:MM} format.  \edef\contextformat {\jobname} -\edef\contextversion{2020.12.08 18:41} +\edef\contextversion{2020.12.09 10:48}  %D Kind of special: diff --git a/tex/context/base/mkiv/mlib-ctx.lua b/tex/context/base/mkiv/mlib-ctx.lua index 4e3d05654..a95359b93 100644 --- a/tex/context/base/mkiv/mlib-ctx.lua +++ b/tex/context/base/mkiv/mlib-ctx.lua @@ -409,6 +409,7 @@ mp = mp or {  -- system namespace      get    = { },      aux    = { },      scan   = { }, +    skip   = { },      inject = { },  } diff --git a/tex/context/base/mkiv/mlib-mpf.lua b/tex/context/base/mkiv/mlib-mpf.lua index 1e381b7c8..00ebb1ae5 100644 --- a/tex/context/base/mkiv/mlib-mpf.lua +++ b/tex/context/base/mkiv/mlib-mpf.lua @@ -34,8 +34,6 @@ local inject = mp.inject  do -    local lmtxmode       = CONTEXTLMTXMODE > 0 -- just a simple one, no need for separation -      -- serializers      local f_integer      = formatters["%i"] @@ -187,25 +185,12 @@ do              result = f()              if result then                  local t = type(result) -                if lmtxmode then -                    -- we can consider to use the injector for tables but then we need to -                    -- check of concatination is expected so best keep this! -                    if t == "number" or t == "boolean" then -                        -- native types -                    elseif t == "string" or t == "table" then -                        -- (concatenated) passed to scantokens -                    else -                        -- scantokens -                        result = tostring(result) -                    end +                if t == "number" then +                    result = f_numeric(result) +                elseif t == "table" then +                    result = concat(result) -- no spaces here                  else -                    if t == "number" then -                        result = f_numeric(result) -                    elseif t == "table" then -                        result = concat(result) -- no spaces here -                    else -                        result = tostring(result) -                    end +                    result = tostring(result)                  end                  if trace_luarun then                      report_luarun("%i: %s result: %s",nesting,t,result) @@ -256,22 +241,8 @@ do          return result      end -    if CONTEXTLMTXMODE > 0 then - -        function metapost.nofscriptruns() -            local c = mplib.getcallbackstate() -            return c.count, string.format( -                "%s (file: %s, text: %s, script: %s, log: %s)", -                c.count, c.file, c.text, c.script, c.log -            ) -        end - -    else - -        function metapost.nofscriptruns() -            return runs -        end - +    function metapost.nofscriptruns() +        return runs      end      -- writers @@ -929,43 +900,44 @@ end  do -    local getmacro  = tex.getmacro -    local getdimen  = tex.getdimen -    local getcount  = tex.getcount -    local gettoks   = tex.gettoks -    local setmacro  = tex.setmacro -    local setdimen  = tex.setdimen -    local setcount  = tex.setcount -    local settoks   = tex.settoks +    local getmacro = tokens.getters.macro +    local setmacro = tokens.setters.macro -    local mpprint   = mp.print -    local mpquoted  = mp.quoted +    local getdimen = tex.getdimen +    local getcount = tex.getcount +    local gettoks  = tex.gettoks +    local setdimen = tex.setdimen +    local setcount = tex.setcount +    local settoks  = tex.settoks -    local bpfactor  = number.dimenfactors.bp +    local mpprint  = mp.print +    local mpquoted = mp.quoted + +    local bpfactor = number.dimenfactors.bp      -- more helpers -    local function getmacro(k)   mpprint (getmacro(k)) end -    local function getdimen(k)   mpprint (getdimen(k)*bpfactor) end -    local function getcount(k)   mpprint (getcount(k)) end -    local function gettoks (k)   mpquoted(gettoks (k)) end +    function mp.getmacro(k)   mpquoted(getmacro(k)) end +    function mp.getdimen(k)   mpprint (getdimen(k)*bpfactor) end +    function mp.getcount(k)   mpprint (getcount(k)) end +    function mp.gettoks (k)   mpquoted(gettoks (k)) end -    local function setmacro(k,v) setmacro(k,v) end -    local function setdimen(k,v) setdimen(k,v/bpfactor) end -    local function setcount(k,v) setcount(k,v) end -    local function settoks (k,v) settoks (k,v) end +    function mp.setmacro(k,v) setmacro(k,v) end +    function mp.setdimen(k,v) setdimen(k,v/bpfactor) end +    function mp.setcount(k,v) setcount(k,v) end +    function mp.settoks (k,v) settoks (k,v) end      -- def foo = lua.mp.foo ... enddef ; % loops due to foo in suffix -    mp._get_macro_ = getmacro   mp.getmacro = getmacro -    mp._get_dimen_ = getdimen   mp.getdimen = getdimen -    mp._get_count_ = getcount   mp.getcount = getcount -    mp._get_toks_  = gettoks    mp.gettoks  = gettoks +    mp._get_macro_ = mp.getmacro +    mp._get_dimen_ = mp.getdimen +    mp._get_count_ = mp.getcount +    mp._get_toks_  = mp.gettoks -    mp._set_macro_ = setmacro   mp.setmacro = setmacro -    mp._set_dimen_ = setdimen   mp.setdimen = setdimen -    mp._set_count_ = setcount   mp.setcount = setcount -    mp._set_toks_  = settoks    mp.settoks  = settoks +    mp._set_macro_ = mp.setmacro +    mp._set_dimen_ = mp.setdimen +    mp._set_count_ = mp.setcount +    mp._set_toks_  = mp.settoks  end @@ -1244,6 +1216,9 @@ do      -- if needed we can optimize the sub (cache last split) +    local mpnumeric = mp.numeric +    local mpquoted  = mp.quoted +      local utflen = utf.len      local utfsub = utf.sub @@ -1252,7 +1227,7 @@ do      end      function mp.utfsub(s,f,t) -        mpquoted(utfsub(s,f,t or f)) +        mpquoted(utfsub(s,f,t))      end  end diff --git a/tex/context/base/mkiv/status-files.pdf b/tex/context/base/mkiv/status-files.pdfBinary files differ index da580c467..8ba106da0 100644 --- a/tex/context/base/mkiv/status-files.pdf +++ b/tex/context/base/mkiv/status-files.pdf diff --git a/tex/context/base/mkiv/status-lua.pdf b/tex/context/base/mkiv/status-lua.pdfBinary files differ index 063329df8..92073e370 100644 --- a/tex/context/base/mkiv/status-lua.pdf +++ b/tex/context/base/mkiv/status-lua.pdf diff --git a/tex/context/base/mkxl/cont-new.mkxl b/tex/context/base/mkxl/cont-new.mkxl index 2f27d306e..187b14f89 100644 --- a/tex/context/base/mkxl/cont-new.mkxl +++ b/tex/context/base/mkxl/cont-new.mkxl @@ -13,7 +13,7 @@  % \normalend % uncomment this to get the real base runtime -\newcontextversion{2020.12.08 18:41} +\newcontextversion{2020.12.09 10:48}  %D This file is loaded at runtime, thereby providing an excellent place for hacks,  %D patches, extensions and new features. There can be local overloads in cont-loc diff --git a/tex/context/base/mkxl/context.mkxl b/tex/context/base/mkxl/context.mkxl index f0b07a3ad..959f6951d 100644 --- a/tex/context/base/mkxl/context.mkxl +++ b/tex/context/base/mkxl/context.mkxl @@ -29,7 +29,7 @@  %D {YYYY.MM.DD HH:MM} format.  \immutable\edef\contextformat {\jobname} -\immutable\edef\contextversion{2020.12.08 18:41} +\immutable\edef\contextversion{2020.12.09 10:48}  %overloadmode 1 % check frozen / warning  %overloadmode 2 % check frozen / error diff --git a/tex/context/base/mkxl/mlib-ctx.lmt b/tex/context/base/mkxl/mlib-ctx.lmt new file mode 100644 index 000000000..a95359b93 --- /dev/null +++ b/tex/context/base/mkxl/mlib-ctx.lmt @@ -0,0 +1,431 @@ +if not modules then modules = { } end modules ['mlib-ctx'] = { +    version   = 1.001, +    comment   = "companion to mlib-ctx.mkiv", +    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL", +    copyright = "PRAGMA ADE / ConTeXt Development Team", +    license   = "see context related readme files", +} + +local type, tostring = type, tostring +local format, concat = string.format, table.concat +local settings_to_hash = utilities.parsers.settings_to_hash +local formatters = string.formatters + +local report_metapost = logs.reporter ("metapost") +local status_metapost = logs.messenger("metapost") + +local starttiming     = statistics.starttiming +local stoptiming      = statistics.stoptiming + +local trace_graphic   = false + +trackers.register("metapost.graphics", +    function(v) trace_graphic = v end +); + +local mplib            = mplib + +metapost               = metapost or { } +local metapost         = metapost +local context          = context + +local setters          = tokens.setters +local setmacro         = setters.macro +local implement        = interfaces.implement + +local v_no             = interfaces.variables.no + +local extensiondata    = metapost.extensiondata or storage.allocate { } +metapost.extensiondata = extensiondata + +storage.register("metapost/extensiondata",extensiondata,"metapost.extensiondata") + +function metapost.setextensions(instances,data) +    if data and data ~= "" then +        extensiondata[#extensiondata+1] = { +            usedinall  = not instances or instances == "", +            instances  = settings_to_hash(instances or ""), +            extensions = data, +        } +    end +end + +function metapost.getextensions(instance,state) +    if state and state == v_no then +        return "" +    else +        local t = { } +        for i=1,#extensiondata do +            local e = extensiondata[i] +            local status = e.instances[instance] +            if (status ~= true) and (e.usedinall or status) then +                t[#t+1] = e.extensions +                e.instances[instance] = true +            end +        end +        return concat(t," ") +    end +end + +implement { +    name      = "setmpextensions", +    actions   = metapost.setextensions, +    arguments = "2 strings", +} + +implement { +    name      = "getmpextensions", +    actions   = { metapost.getextensions, context } , +    arguments = "string" +} + +local patterns = { +    CONTEXTLMTXMODE > 0 and "meta-imp-%s.mkxl" or "", +    "meta-imp-%s.mkiv", +    "meta-imp-%s.tex", +    -- obsolete: +    "meta-%s.mkiv", +    "meta-%s.tex" +} + +local function action(name,foundname) +    commands.loadlibrary(name,foundname,false) +    status_metapost("library %a is loaded",name) +end + +local function failure(name) +    report_metapost("library %a is unknown or invalid",name) +end + +implement { +    name      = "useMPlibrary", +    arguments = "string", +    actions   = function(name) +        resolvers.uselibrary { +            name     = name, +            patterns = patterns, +            action   = action, +            failure  = failure, +            onlyonce = true, +        } +    end +} + +-- metapost.variables = { } -- to be stacked + +implement { +    name      = "mprunvar", +    arguments = "string", +    actions   = function(name) +        local value = metapost.variables[name] +        if value ~= nil then +            local tvalue = type(value) +            if tvalue == "table" then +                context(concat(value," ")) +            elseif tvalue == "number" or tvalue == "boolean" then +                context(tostring(value)) +            elseif tvalue == "string" then +                context(value) +            end +        end +    end +} + +implement { +    name      = "mpruntab", +    arguments = { "string", "integer" }, +    actions   = function(name,n) +        local value = metapost.variables[name] +        if value ~= nil then +            local tvalue = type(value) +            if tvalue == "table" then +                context(value[n]) +            elseif tvalue == "number" or tvalue == "boolean" then +                context(tostring(value)) +            elseif tvalue == "string" then +                context(value) +            end +        end +    end +} + +implement { +    name      = "mprunset", +    arguments = "2 strings", +    actions   = function(name,connector) +        local value = metapost.variables[name] +        if value ~= nil then +            local tvalue = type(value) +            if tvalue == "table" then +                context(concat(value,connector)) +            elseif tvalue == "number" or tvalue == "boolean" then +                context(tostring(value)) +            elseif tvalue == "string" then +                context(value) +            end +        end +    end +} + +-- we need to move more from pps to here as pps is the plugin .. the order is a mess +-- or just move the scanners to pps + +function metapost.graphic(specification) +    metapost.pushformat(specification) +    metapost.graphic_base_pass(specification) +    metapost.popformat() +end + +function metapost.startgraphic(t) +    if not t then +        t = { } +    end +    if not t.instance then +        t.instance = metapost.defaultinstance +    end +    if not t.format then +        t.format = metapost.defaultformat +    end +    if not t.method then +        t.method = metapost.defaultmethod +    end +    t.data = { } +    return t +end + +function metapost.stopgraphic(t) +    if t then +        t.data = concat(t.data or { },"\n") +        if trace_graphic then +            report_metapost("\n"..t.data.."\n") +        end +        metapost.graphic(t) +    end +end + +function metapost.tographic(t,f,s,...) +    local d = t.data +    d[#d+1] = s and formatters[f](s,...) or f +end + +implement { +    name      = "mpgraphic", +    actions   = metapost.graphic, +    arguments = { +        { +            { "instance" }, +            { "format" }, +            { "data" }, +            { "initializations" }, +            { "extensions" }, +            { "inclusions" }, +            { "definitions" }, +            { "figure" }, +            { "method" }, +            { "namespace" }, +        } +    } +} + +implement { +    name      = "mpsetoutercolor", +    actions   = function(...) metapost.setoutercolor(...) end, -- not yet implemented +    arguments = { "integer", "integer", "integer", "integer" } +} + +implement { +    name      = "mpflushreset", +    actions   = function() metapost.flushreset() end -- not yet implemented +} + +implement { +    name      = "mpflushliteral", +    actions   = function(str) metapost.flushliteral(str) end, -- not yet implemented +    arguments = "string", +} + +-- this has to become a codeinjection + +function metapost.getclippath(specification) -- why not a special instance for this +    local mpx  = metapost.pushformat(specification) +    local data = specification.data or "" +    if mpx and data ~= "" then +        starttiming(metapost) +        starttiming(metapost.exectime) +        local result = mpx:execute ( format ( "%s;%s;beginfig(1);%s;%s;endfig;", +            specification.extensions or "", +            specification.inclusions or "", +            specification.initializations or "", +            data +        ) ) +        stoptiming(metapost.exectime) +        if result.status > 0 then +            report_metapost("%s: %s", result.status, result.error or result.term or result.log) +            result = nil +        else +            result = metapost.filterclippath(result) +        end +        stoptiming(metapost) +        metapost.pushformat() +        return result +    else +        metapost.pushformat() +    end +end + +function metapost.filterclippath(result) +    if result then +        local figures = result.fig +        if figures and #figures > 0 then +            local figure = figures[1] +            local objects = figure:objects() +            if objects then +                local lastclippath +                for o=1,#objects do +                    local object = objects[o] +                    if object.type == "start_clip" then +                        lastclippath = object.path +                    end +                end +                return lastclippath +            end +        end +    end +end + +function metapost.theclippath(...) +    local result = metapost.getclippath(...) +    if result then -- we could just print the table +        return concat(metapost.flushnormalpath(result)," ") +    else +        return "" +    end +end + +implement { +    name      = "mpsetclippath", +    actions   = function(specification) +        local p = specification.data and metapost.theclippath(specification) +        if not p or p == "" then +            local b = number.dimenfactors.bp +            local w = b * (specification.width or 0) +            local h = b * (specification.height or 0) +            p = formatters["0 0 m %.6N 0 l %.6N %.6N l 0 %.6N l"](w,w,h,h) +        end +        setmacro("MPclippath",p,"global") +    end, +    arguments = { +        { +            { "instance" }, +            { "format" }, +            { "data" }, +            { "initializations" }, +            { "useextensions" }, +            { "inclusions" }, +            { "method" }, +            { "namespace" }, +            { "width", "dimension" }, +            { "height", "dimension" }, +        }, +    } +} + +statistics.register("metapost", function() +    local n = metapost.nofruns +    if n and n > 0 then +        local elapsedtime = statistics.elapsedtime +        local elapsed     = statistics.elapsed +        local runs, stats = metapost.nofscriptruns() +        local instances, +              memory      = metapost.getstatistics(true) +        return format("%s seconds, loading: %s, execution: %s, n: %s, average: %s, instances: %i, luacalls: %s, memory: %0.3f M", +            elapsedtime(metapost), elapsedtime(mplib), elapsedtime(metapost.exectime), n, +            elapsedtime((elapsed(metapost) + elapsed(mplib) + elapsed(metapost.exectime)) / n), +            instances, stats and stats or runs, memory/(1024*1024)) +    else +        return nil +    end +end) + +-- only used in graphictexts + +metapost.tex = metapost.tex or { } +local mptex  = metapost.tex + +local environments = { } + +function mptex.set(str) +    environments[#environments+1] = str +end + +function mptex.setfrombuffer(name) +    environments[#environments+1] = buffers.getcontent(name) +end + +function mptex.get() +    return concat(environments,"\n") +end + +function mptex.reset() +    environments = { } +end + +implement { +    name      = "mppushvariables", +    actions   = metapost.pushvariables, +} + +implement { +    name      = "mppopvariables", +    actions   = metapost.popvariables, +} + +implement { +    name      = "mptexset", +    arguments = "string", +    actions   = mptex.set +} + +implement { +    name      = "mptexsetfrombuffer", +    arguments = "string", +    actions   = mptex.setfrombuffer +} + +implement { +    name      = "mptexget", +    actions   = { mptex.get, context } +} + +implement { +    name      = "mptexreset", +    actions   = mptex.reset +} + +-- moved from mlib-lua: + +mp = mp or {  -- system namespace +    set    = { }, +    get    = { }, +    aux    = { }, +    scan   = { }, +    skip   = { }, +    inject = { }, +} + +MP = MP or { } -- user namespace + +-- We had this: +-- +--   table.setmetatablecall(mp,function(t,k) mpprint(k) end) +-- +-- but the next one is more interesting because we cannot use calls like: +-- +--   lua.mp.somedefdname("foo") +-- +-- which is due to expansion of somedefdname during suffix creation. So: +-- +--   lua.mp("somedefdname","foo") + +table.setmetatablecall(mp,function(t,k,...) return t[k](...) end) +table.setmetatablecall(MP,function(t,k,...) return t[k](...) end) diff --git a/tex/context/base/mkxl/mlib-ctx.mkxl b/tex/context/base/mkxl/mlib-ctx.mkxl index 88f26c9ac..81fbffa85 100644 --- a/tex/context/base/mkxl/mlib-ctx.mkxl +++ b/tex/context/base/mkxl/mlib-ctx.mkxl @@ -14,10 +14,10 @@  \writestatus{loading}{MetaPost Library Graphics / Initializations}  \registerctxluafile{mlib-fio}{autosuffix} -\registerctxluafile{mlib-run}{} -\registerctxluafile{mlib-ctx}{} +\registerctxluafile{mlib-run}{autosuffix} +\registerctxluafile{mlib-ctx}{autosuffix}  \registerctxluafile{mlib-lua}{autosuffix} -\registerctxluafile{mlib-mpf}{} +\registerctxluafile{mlib-mpf}{autosuffix}  \registerctxluafile{mlib-scn}{autosuffix}  \registerctxluafile{mlib-mat}{autosuffix}  \registerctxluafile{mlib-ran}{autosuffix} diff --git a/tex/context/base/mkxl/mlib-lua.lmt b/tex/context/base/mkxl/mlib-lua.lmt index 8721ed60c..c4a1965a2 100644 --- a/tex/context/base/mkxl/mlib-lua.lmt +++ b/tex/context/base/mkxl/mlib-lua.lmt @@ -9,7 +9,17 @@ if not modules then modules = { } end modules ['mlib-lua'] = {  local type = type  local insert, remove = table.insert, table.remove +local codes = mplib.getcodes() +local types = mplib.gettypes() + +table.hashed(codes) +table.hashed(types) + +metapost.codes = codes +metapost.types = types +  local scan   = mp.scan +local skip   = mp.skip  local inject = mp.inject  local currentmpx = nil @@ -30,6 +40,8 @@ local scan_transform  = mplib.scan_transform  local scan_path       = mplib.scan_path  local scan_pen        = mplib.scan_pen +local skip_token      = mplib.skip_token +  scan.next       = function(k)   return scan_next      (currentmpx,k)   end  scan.expression = function(k)   return scan_expression(currentmpx,k)   end  scan.token      = function(k)   return scan_token     (currentmpx,k)   end @@ -45,6 +57,8 @@ scan.transform  = function(t)   return scan_transform (currentmpx,t)   end  scan.path       = function(t)   return scan_path      (currentmpx,t)   end  scan.pen        = function(t)   return scan_pen       (currentmpx,t)   end +skip.token      = function(t)   return skip_token     (currentmpx,t)   end +  local solvepath = mplib.solvepath  local getstatus = mplib.getstatus @@ -74,6 +88,9 @@ inject.cmykcolor = function(c,m,y,k)         return inject_cmykcolor(currentmpx,  inject.transform = function(x,y,xx,xy,yx,yy) return inject_transform(currentmpx,x,y,xx,xy,yx,yy) end  inject.whatever  = function(...)             return inject_whatever (currentmpx,...)             end +inject.triplet    = inject.color +inject.quadruplet = inject.cmykcolor +  local function same(p,n)      local f = p[1]      local l = p[n] diff --git a/tex/context/base/mkxl/mlib-mpf.lmt b/tex/context/base/mkxl/mlib-mpf.lmt new file mode 100644 index 000000000..6bd31376c --- /dev/null +++ b/tex/context/base/mkxl/mlib-mpf.lmt @@ -0,0 +1,1231 @@ +if not modules then modules = { } end modules ['mlib-mpf'] = { +    version   = 1.001, +    comment   = "companion to mlib-ctx.mkiv", +    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL", +    copyright = "PRAGMA ADE / ConTeXt Development Team", +    license   = "see context related readme files", +} + +-- moved from mlib-lua: + +local type, tostring, tonumber, select, loadstring = type, tostring, tonumber, select, loadstring +local find, match, gsub, gmatch = string.find, string.match, string.gsub, string.gmatch +local concat = table.concat + +local formatters   = string.formatters +local lpegmatch    = lpeg.match +local lpegpatterns = lpeg.patterns + +local P, S, Ct, Cs, Cc, C = lpeg.P, lpeg.S, lpeg.Ct, lpeg.Cs, lpeg.Cc, lpeg.C + +local report_luarun  = logs.reporter("metapost","lua") +local report_script  = logs.reporter("metapost","script") +local report_message = logs.reporter("metapost") + +local trace_luarun   = false  trackers.register("metapost.lua",function(v) trace_luarun = v end) + +local be_tolerant    = true   directives.register("metapost.lua.tolerant", function(v) be_tolerant = v end) + +local set    = mp.set +local get    = mp.get +local aux    = mp.aux +local scan   = mp.scan +local skip   = mp.skip +local inject = mp.inject + +do + +    -- serializers + +    local f_integer      = formatters["%i"] +    local f_numeric      = formatters["%F"] + +    -- no %n as that can produce -e notation and that is not so nice for scaled butmaybe we +    -- should then switch between ... i.e. make a push/pop for the formatters here ... not now. + +    local f_integer      = formatters["%i"] +    local f_numeric      = formatters["%F"] +    local f_pair         = formatters["(%F,%F)"] +    local f_ctrl         = formatters["(%F,%F) .. controls (%F,%F) and (%F,%F)"] +    local f_triplet      = formatters["(%F,%F,%F)"] +    local f_quadruple    = formatters["(%F,%F,%F,%F)"] +    local f_transform    = formatters["totransform(%F,%F,%F,%F,%F,%F)"] +    local f_pen          = formatters["(pencircle transformed totransform(%F,%F,%F,%F,%F,%F))"] + +    local f_points       = formatters["%p"] +    local f_pair_pt      = formatters["(%p,%p)"] +    local f_ctrl_pt      = formatters["(%p,%p) .. controls (%p,%p) and (%p,%p)"] +    local f_triplet_pt   = formatters["(%p,%p,%p)"] +    local f_quadruple_pt = formatters["(%p,%p,%p,%p)"] + +    local r = P('%')  / "percent" +            + P('"')  / "dquote" +            + P('\n') / "crlf" +         -- + P(' ')  / "space" +    local a = Cc("&") +    local q = Cc('"') +    local p = Cs(q * (r * a)^-1 * (a * r * (P(-1) + a) + P(1))^0 * q) + +    mp.cleaned = function(s) return lpegmatch(p,s) or s end + +    -- management + +    -- sometimes we gain (e.g. .5 sec on the sync test) + +    local cache = table.makeweak() + +    local runscripts = { } +    local runnames   = { } +    local nofscripts = 0 + +    function metapost.registerscript(name,f) +        nofscripts = nofscripts + 1 +        if f then +            runscripts[nofscripts] = f +            runnames[name] = nofscripts +        else +            runscripts[nofscripts] = name +        end +-- print("set",name,nofscripts,f) +        return nofscripts +    end + +    function metapost.scriptindex(name) +-- print("get",name,runnames[name] or 0) +        return runnames[name] or 0 +    end + +    -- The gbuffer sharing and such is not really needed now but make a dent when +    -- we have a high volume of simpel calls (loops) so we keep it around for a +    -- while. + +    local nesting = 0 +    local runs    = 0 +    local gbuffer = { } +    local buffer  = gbuffer +    local n       = 0 + +    local function mpdirect1(a) +        n = n + 1 buffer[n] = a +    end +    local function mpdirect2(a,b) +        n = n + 1 buffer[n] = a +        n = n + 1 buffer[n] = b +    end +    local function mpdirect3(a,b,c) +        n = n + 1 buffer[n] = a +        n = n + 1 buffer[n] = b +        n = n + 1 buffer[n] = c +    end +    local function mpdirect4(a,b,c,d) +        n = n + 1 buffer[n] = a +        n = n + 1 buffer[n] = b +        n = n + 1 buffer[n] = c +        n = n + 1 buffer[n] = d +    end +    local function mpdirect5(a,b,c,d,e) +        n = n + 1 buffer[n] = a +        n = n + 1 buffer[n] = b +        n = n + 1 buffer[n] = c +        n = n + 1 buffer[n] = d +        n = n + 1 buffer[n] = e +    end + +    local function mpflush(separator) +        buffer[1] = concat(buffer,separator or "",1,n) +        n = 1 +    end + +    function metapost.runscript(code) +        nesting = nesting + 1 +        runs    = runs + 1 + +        local index = type(code) == "number" +        local f +        local result + +        if index then +            f = runscripts[code] +            if not f then +                report_luarun("%i: bad index: %s",nesting,code) +            elseif trace_luarun then +                report_luarun("%i: index: %i",nesting,code) +            end +        else +            if trace_luarun then +                report_luarun("%i: code: %s",nesting,code) +            end +            f = cache[code] +            if not f then +                f = loadstring("return " .. code) +                if f then +                    cache[code] = f +                elseif be_tolerant then +                    f = loadstring(code) +                    if f then +                        cache[code] = f +                    end +                end +            end +        end + +        -- returning nil is more efficient and a signal not to scan in mp + +        if f then + +            local lbuffer, ln + +            if nesting == 1 then +                buffer = gbuffer +                n      = 0 +            else +                lbuffer = buffer +                ln      = n +                buffer  = { } +                n       = 0 +            end + +            result = f() +            if result then +                local t = type(result) +                -- we can consider to use the injector for tables but then we need to +                -- check if concatination is expected so best keep this! +                if t == "number" or t == "boolean" then +                    -- native types +                elseif t == "string" or t == "table" then +                    -- (concatenated) passed to scantokens +                else +                    -- scantokens +                    result = tostring(result) +                end +                if trace_luarun then +                    report_luarun("%i: %s result: %s",nesting,t,result) +                end +            elseif n == 0 then +             -- result = "" +                result = nil -- no scantokens done then +                if trace_luarun then +                    report_luarun("%i: no buffered result",nesting) +                end +            elseif n == 1 then +                result = buffer[1] +                if trace_luarun then +                    report_luarun("%i: 1 buffered result: %s",nesting,result) +                end +            else +                -- the space is why we sometimes have collectors +                if nesting == 1 then +                    -- if we had no space we could pass result directly in lmtx +                    result = concat(buffer," ",1,n) +                    if n > 500 or #result > 10000 then +                        gbuffer = { } -- newtable(20,0) +                        lbuffer = gbuffer +                    end +                else +                    -- if we had no space we could pass result directly in lmtx +                    result = concat(buffer," ") +                end +                if trace_luarun then +                    report_luarun("%i: %i buffered results: %s",nesting,n,result) +                end +            end + +            if nesting == 1 then +                n = 0 +            else +                buffer = lbuffer +                n      = ln +            end + +        else +            report_luarun("%i: no result, invalid code: %s",nesting,code) +            result = "" +        end + +        nesting = nesting - 1 + +        return result +    end + +    function metapost.nofscriptruns() +        local c = mplib.getcallbackstate() +        return c.count, string.format( +            "%s (file: %s, text: %s, script: %s, log: %s)", +            c.count, c.file, c.text, c.script, c.log +        ) +    end + +    -- writers + +    local function mpp(value) +        n = n + 1 +        local t = type(value) +        if t == "number" then +            buffer[n] = f_numeric(value) +        elseif t == "string" then +            buffer[n] = value +        elseif t == "table" then +            if #t == 6 then +                buffer[n] = "totransform(" .. concat(value,",") .. ")" +            else +                buffer[n] = "(" .. concat(value,",") .. ")" +            end +        else -- boolean or whatever +            buffer[n] = tostring(value) +        end +    end + +    local function mpprint(first,second,...) +        if second == nil then +            if first ~= nil then +                mpp(first) +            end +        else +            for i=1,select("#",first,second,...) do +                local value = (select(i,first,second,...)) +                if value ~= nil then +                    mpp(value) +                end +            end +        end +    end + +    local function mpp(value) +        n = n + 1 +        local t = type(value) +        if t == "number" then +            buffer[n] = f_numeric(value) +        elseif t == "string" then +            buffer[n] = lpegmatch(p,value) +        elseif t == "table" then +            if #t > 4 then +                buffer[n] = "" +            else +                buffer[n] = "(" .. concat(value,",") .. ")" +            end +        else -- boolean or whatever +            buffer[n] = tostring(value) +        end +    end + +    local function mpvprint(first,second,...) -- variable print +        if second == nil then +            if first ~= nil then +                mpp(first) +            end +        else +            for i=1,select("#",first,second,...) do +                local value = (select(i,first,second,...)) +                if value ~= nil then +                    mpp(value) +                end +            end +        end +    end + +    local function mpstring(value) +        n = n + 1 +        buffer[n] = lpegmatch(p,value) +    end + +    local function mpboolean(b) +        n = n + 1 +        buffer[n] = b and "true" or "false" +    end + +    local function mpnumeric(f) +        n = n + 1 +        if not f or f == 0 then +            buffer[n] = "0" +        else +            buffer[n] = f_numeric(f) +        end +    end + +    local function mpinteger(i) +        n = n + 1 +     -- buffer[n] = i and f_integer(i) or "0" +        buffer[n] = i or "0" +    end + +    local function mppoints(i) +        n = n + 1 +        if not i or i == 0 then +            buffer[n] = "0pt" +        else +            buffer[n] = f_points(i) +        end +    end + +    local function mppair(x,y) +        n = n + 1 +        if type(x) == "table" then +            buffer[n] = f_pair(x[1],x[2]) +        else +            buffer[n] = f_pair(x,y) +        end +    end + +    local function mppairpoints(x,y) +        n = n + 1 +        if type(x) == "table" then +            buffer[n] = f_pair_pt(x[1],x[2]) +        else +            buffer[n] = f_pair_pt(x,y) +        end +    end + +    local function mptriplet(x,y,z) +        n = n + 1 +        if type(x) == "table" then +            buffer[n] = f_triplet(x[1],x[2],x[3]) +        else +            buffer[n] = f_triplet(x,y,z) +        end +    end + +    local function mptripletpoints(x,y,z) +        n = n + 1 +        if type(x) == "table" then +            buffer[n] = f_triplet_pt(x[1],x[2],x[3]) +        else +            buffer[n] = f_triplet_pt(x,y,z) +        end +    end + +    local function mpquadruple(w,x,y,z) +        n = n + 1 +        if type(w) == "table" then +            buffer[n] = f_quadruple(w[1],w[2],w[3],w[4]) +        else +            buffer[n] = f_quadruple(w,x,y,z) +        end +    end + +    local function mpquadruplepoints(w,x,y,z) +        n = n + 1 +        if type(w) == "table" then +            buffer[n] = f_quadruple_pt(w[1],w[2],w[3],w[4]) +        else +            buffer[n] = f_quadruple_pt(w,x,y,z) +        end +    end + +    local function mptransform(x,y,xx,xy,yx,yy) +        n = n + 1 +        if type(x) == "table" then +            buffer[n] = f_transform(x[1],x[2],x[3],x[4],x[5],x[6]) +        else +            buffer[n] = f_transform(x,y,xx,xy,yx,yy) +        end +    end + +    local function mpcolor(c,m,y,k) +        n = n + 1 +        if type(c) == "table" then +            local l = #c +            if l == 4 then +                buffer[n] = f_quadruple(c[1],c[2],c[3],c[4]) +            elseif l == 3 then +                buffer[n] = f_triplet(c[1],c[2],c[3]) +            else +                buffer[n] = f_numeric(c[1]) +            end +        else +            if k then +                buffer[n] = f_quadruple(c,m,y,k) +            elseif y then +                buffer[n] = f_triplet(c,m,y) +            else +                buffer[n] = f_numeric(c) +            end +        end +    end + +    -- we have three kind of connectors: +    -- +    -- .. ... -- (true) + +    local function mp_path(f2,f6,t,connector,cycle) +        if type(t) == "table" then +            local tn = #t +            if tn == 1 then +                local t1 = t[1] +                n = n + 1 +                if t.pen then +                    buffer[n] = f_pen(unpack(t1)) +                else +                    buffer[n] = f2(t1[1],t1[2]) +                end +            elseif tn > 0 then +                if connector == true or connector == nil then +                    connector = ".." +                elseif connector == false then +                    connector = "--" +                end +                if cycle == nil then +                    cycle = t.cycle +                    if cycle == nil then +                        cycle = true +                    end +                end +                local six      = connector == ".." -- otherwise we use whatever gets asked for +                local controls = connector         -- whatever +                local a = t[1] +                local b = t[2] +                n = n + 1 +                buffer[n] = "(" +                n = n + 1 +                if six and #a == 6 and #b == 6 then +                    buffer[n] = f6(a[1],a[2],a[5],a[6],b[3],b[4]) +                    controls  = ".." +                else +                    buffer[n] = f2(a[1],a[2]) +                    controls  = connector +                end +                for i=2,tn-1 do +                    a = b +                    b = t[i+1] +                    n = n + 1 +                    buffer[n] = connector +                    n = n + 1 +                    if six and #a == 6 and #b == 6 then +                        buffer[n] = f6(a[1],a[2],a[5],a[6],b[3],b[4]) +                        controls  = ".." +                    else +                        buffer[n] = f2(a[1],a[2]) +                        controls  = connector +                    end +                end +                n = n + 1 +                buffer[n] = connector +                a = b +                b = t[1] +                n = n + 1 +                if cycle then +                    if six and #a == 6 and #b == 6 then +                        buffer[n] = f6(a[1],a[2],a[5],a[6],b[3],b[4]) +                        controls  = ".." +                    else +                        buffer[n] = f2(a[1],a[2]) +                        controls  = connector +                    end +                    n = n + 1 +                    buffer[n] = connector +                    n = n + 1 +                    buffer[n] = "cycle" +                else +                    buffer[n] = f2(a[1],a[2]) +                end +                n = n + 1 +                buffer[n] = ")" +            end +        end +    end + +    local function mppath(...) +        mp_path(f_pair,f_ctrl,...) +    end + +    local function mppathpoints(...) +        mp_path(f_pair_pt,f_ctrl_pt,...) +    end + +    local function mpsize(t) +        n = n + 1 +        buffer[n] = type(t) == "table" and f_numeric(#t) or "0" +    end + +    local replacer = lpeg.replacer("@","%%") + +    local function mpfprint(fmt,...) +        n = n + 1 +        if not find(fmt,"%",1,true) then +            fmt = lpegmatch(replacer,fmt) +        end +        buffer[n] = formatters[fmt](...) +    end + +    local function mpquoted(fmt,s,...) +        if s then +            n = n + 1 +            if not find(fmt,"%",1,true) then +                fmt = lpegmatch(replacer,fmt) +            end +         -- buffer[n] = '"' .. formatters[fmt](s,...) .. '"' +            buffer[n] = lpegmatch(p,formatters[fmt](s,...)) +        elseif fmt then +            n = n + 1 +         -- buffer[n] = '"' .. fmt .. '"' +            buffer[n] = lpegmatch(p,fmt) +        else +            -- something is wrong +        end +    end + +    aux.direct          = mpdirect1 +    aux.direct1         = mpdirect1 +    aux.direct2         = mpdirect2 +    aux.direct3         = mpdirect3 +    aux.direct4         = mpdirect4 +    aux.flush           = mpflush + +    aux.print           = mpprint +    aux.vprint          = mpvprint +    aux.boolean         = mpboolean +    aux.string          = mpstring +    aux.numeric         = mpnumeric +    aux.number          = mpnumeric +    aux.integer         = mpinteger +    aux.points          = mppoints +    aux.pair            = mppair +    aux.pairpoints      = mppairpoints +    aux.triplet         = mptriplet +    aux.tripletpoints   = mptripletpoints +    aux.quadruple       = mpquadruple +    aux.quadruplepoints = mpquadruplepoints +    aux.path            = mppath +    aux.pathpoints      = mppathpoints +    aux.size            = mpsize +    aux.fprint          = mpfprint +    aux.quoted          = mpquoted +    aux.transform       = mptransform +    aux.color           = mpcolor + +    -- for the moment + +    local function mpdraw(lines,list) -- n * 4 +        if list then +            local c = #lines +            for i=1,c do +                local ci = lines[i] +                local ni = #ci +                n = n + 1 buffer[n] = i < c and "d(" or "D(" +                for j=1,ni,2 do +                    local l = j + 1 +                    n = n + 1 buffer[n] = ci[j] +                    n = n + 1 buffer[n] = "," +                    n = n + 1 buffer[n] = ci[l] +                    n = n + 1 buffer[n] = l < ni and ")--(" or ");" +                end +            end +        else +            local l = #lines +            local m = l - 4 +            for i=1,l,4 do +                n = n + 1 buffer[n] = i < m and "d(" or "D(" +                n = n + 1 buffer[n] = lines[i] +                n = n + 1 buffer[n] = "," +                n = n + 1 buffer[n] = lines[i+1] +                n = n + 1 buffer[n] = ")--(" +                n = n + 1 buffer[n] = lines[i+2] +                n = n + 1 buffer[n] = "," +                n = n + 1 buffer[n] = lines[i+3] +                n = n + 1 buffer[n] = ");" +            end +        end +    end + +    local function mpfill(lines,list) +        if list then +            local c = #lines +            for i=1,c do +                local ci = lines[i] +                local ni = #ci +                n = n + 1 buffer[n] = i < c and "f(" or "F(" +                for j=1,ni,2 do +                    local l = j + 1 +                    n = n + 1 buffer[n] = ci[j] +                    n = n + 1 buffer[n] = "," +                    n = n + 1 buffer[n] = ci[l] +                    n = n + 1 buffer[n] = l < ni and ")--(" or ")--C;" +                end +            end +        else +            local l = #lines +            local m = l - 4 +            for i=1,l,4 do +                n = n + 1 buffer[n] = i < m and "f(" or "F(" +                n = n + 1 buffer[n] = lines[i] +                n = n + 1 buffer[n] = "," +                n = n + 1 buffer[n] = lines[i+1] +                n = n + 1 buffer[n] = ")--(" +                n = n + 1 buffer[n] = lines[i+2] +                n = n + 1 buffer[n] = "," +                n = n + 1 buffer[n] = lines[i+3] +                n = n + 1 buffer[n] = ")--C;" +            end +        end +    end + +    aux.draw = mpdraw +    aux.fill = mpfill + +    for k, v in next, aux do mp[k] = v end + +end + +do + +    -- Another experimental feature: + +    local mpnumeric   = mp.numeric +    local scanstring  = scan.string +    local scriptindex = metapost.scriptindex + +    function mp.mf_script_index(name) +        local index = scriptindex(name) +     -- report_script("method %i, name %a, index %i",1,name,index) +        mpnumeric(index) +    end + +    -- once bootstrapped ... (needs pushed mpx instances) + +    metapost.registerscript("scriptindex",function() +        local name  = scanstring() +        local index = scriptindex(name) +     -- report_script("method %i, name %a, index %i",2,name,index) +        mpnumeric(index) +    end) + +end + +-- the next will move to mlib-lmp.lua + +do + +    local mpnamedcolor = attributes.colors.mpnamedcolor +    local mpprint      = aux.print +    local scanstring   = scan.string + +    mp.mf_named_color = function(str) +        mpprint(mpnamedcolor(str)) +    end + +    -- todo: we can inject but currently we always get a string back so then +    -- we need to deal with it upstream in the color module ... not now + +    metapost.registerscript("namedcolor",function() +        mpprint(mpnamedcolor(scanstring())) +    end) + +end + +function mp.n(t) -- used ? +    return type(t) == "table" and #t or 0 +end + +do + +    -- experiment: names can change + +    local mppath     = aux.path +    local mpsize     = aux.size + +    local whitespace = lpegpatterns.whitespace +    local newline    = lpegpatterns.newline +    local setsep     = newline^2 +    local comment    = (S("#%") + P("--")) * (1-newline)^0 * (whitespace - setsep)^0 +    local value      = (1-whitespace)^1 / tonumber +    local entry      = Ct( value * whitespace * value) +    local set        = Ct((entry * (whitespace-setsep)^0 * comment^0)^1) +    local series     = Ct((set * whitespace^0)^1) + +    local pattern    = whitespace^0 * series + +    local datasets   = { } +    mp.datasets      = datasets + +    function mp.dataset(str) +        return lpegmatch(pattern,str) +    end + +    function datasets.load(tag,filename) +        if not filename then +            tag, filename = file.basename(tag), tag +        end +        local data = lpegmatch(pattern,io.loaddata(filename) or "") +        datasets[tag] = { +            data = data, +            line = function(n) mppath(data[n or 1]) end, +            size = function()  mpsize(data)         end, +        } +    end + +    table.setmetatablecall(datasets,function(t,k,f,...) +        local d = datasets[k] +        local t = type(d) +        if t == "table" then +            d = d[f] +            if type(d) == "function" then +                d(...) +            else +                mpvprint(...) +            end +        elseif t == "function" then +            d(f,...) +        end +    end) + +end + +-- \startluacode +--     local str = [[ +--         10 20 20 20 +--         30 40 40 60 +--         50 10 +-- +--         10 10 20 30 +--         30 50 40 50 +--         50 20 -- the last one +-- +--         10 20 % comment +--         20 10 +--         30 40 # comment +--         40 20 +--         50 10 +--     ]] +-- +--     MP.myset = mp.dataset(str) +-- +--     inspect(MP.myset) +-- \stopluacode +-- +-- \startMPpage +--     color c[] ; c[1] := red ; c[2] := green ; c[3] := blue ; +--     for i=1 upto lua("mp.print(mp.n(MP.myset))") : +--         draw lua("mp.path(MP.myset[" & decimal i & "])") withcolor c[i] ; +--     endfor ; +-- \stopMPpage + +-- texts: + +do + +    local mptriplet    = mp.triplet + +    local bpfactor     = number.dimenfactors.bp +    local textexts     = nil +    local mptriplet    = mp.triplet +    local nbdimensions = nodes.boxes.dimensions + +    function mp.mf_tt_initialize(tt) +        textexts = tt +    end + +    function mp.mf_tt_dimensions(n) +        local box = textexts and textexts[n] +        if box then +            -- could be made faster with nuts but not critical +            mptriplet(box.width*bpfactor,box.height*bpfactor,box.depth*bpfactor) +        else +            mptriplet(0,0,0) +        end +    end + +    function mp.mf_tb_dimensions(category,name) +        local w, h, d = nbdimensions(category,name) +        mptriplet(w*bpfactor,h*bpfactor,d*bpfactor) +    end + +    function mp.report(a,b,c,...) +        if c then +            report_message("%s : %s",a,formatters[(gsub(b,"@","%%"))](c,...)) +        elseif b then +            report_message("%s : %s",a,b) +        elseif a then +            report_message("%s : %s","message",a) +        end +    end + +end + +do + +    local mpprint     = aux.print +    local modes       = tex.modes +    local systemmodes = tex.systemmodes + +    function mp.mode(s) +        mpprint(modes[s] and true or false) +    end + +    function mp.systemmode(s) +        mpprint(systemmodes[s] and true or false) +    end + +    mp.processingmode = mp.mode + +end + +-- for alan's nodes: + +do + +    local mpprint  = aux.print +    local mpquoted = aux.quoted + +    function mp.isarray(str) +         mpprint(find(str,"%d") and true or false) +    end + +    function mp.prefix(str) +         mpquoted(match(str,"^(.-)[%d%[]") or str) +    end + +    -- function mp.dimension(str) +    --     local n = 0 +    --     for s in gmatch(str,"%[?%-?%d+%]?") do --todo: lpeg +    --         n = n + 1 +    --     end +    --     mpprint(n) +    -- end + +    mp.dimension = lpeg.counter(P("[") * lpegpatterns.integer * P("]") + lpegpatterns.integer,mpprint) + +    -- faster and okay as we don't have many variables but probably only +    -- basename makes sense and even then it's not called that often + +    -- local hash  = table.setmetatableindex(function(t,k) +    --     local v = find(k,"%d") and true or false +    --     t[k] = v +    --     return v +    -- end) +    -- +    -- function mp.isarray(str) +    --      mpprint(hash[str]) +    -- end +    -- +    -- local hash  = table.setmetatableindex(function(t,k) +    --     local v = '"' .. (match(k,"^(.-)%d") or k) .. '"' +    --     t[k] = v +    --     return v +    -- end) +    -- +    -- function mp.prefix(str) +    --      mpprint(hash[str]) +    -- end + +end + +do + +    local scanstring     = scan.string +    local scannumeric    = scan.numeric +    local skiptoken      = skip.token + +    local injectstring   = inject.string +    local injectnumeric  = inject.numeric + +    local registerscript = metapost.registerscript + +    local comma_code     = metapost.codes.comma + +    local getmacro       = tokens.getters.macro +    local setmacro       = tokens.setters.macro + +    local getdimen       = tex.getdimen +    local getcount       = tex.getcount +    local gettoks        = tex.gettoks +    local setdimen       = tex.setdimen +    local setcount       = tex.setcount +    local settoks        = tex.settoks + +    local bpfactor       = number.dimenfactors.bp + +    -- more helpers + +    registerscript("getmacro", function() injectstring (getmacro(scanstring())) end) +    registerscript("getdimen", function() injectnumeric(getdimen(scanstring())*bpfactor) end) +    registerscript("getcount", function() injectnumeric(getcount(scanstring())) end) +    registerscript("gettoks",  function() injectstring (gettoks (scanstring())) end) + +    registerscript("setmacro", function() setmacro(scanstring(),scanstring()) end) +    registerscript("setdimen", function() setdimen(scanstring(),scannumeric()/bpfactor) end) +    registerscript("setcount", function() setcount(scanstring(),scannumeric()) end) +    registerscript("settoks",  function() settoks (scanstring(),scanstring()) end) + +    local utflen = utf.len +    local utfsub = utf.sub + +    registerscript("utflen", function() +        injectnumeric(utflen(scanstring())) +    end) + +    registerscript("utfsub", function() -- we have an optional third argument so we explicitly scan a text argument +        injectstring(utfsub(scanstring(),skiptoken(comma_code) and scannumeric(),skiptoken(comma_code) and scannumeric())) +    end) + +end + +-- position fun + +do + +    local mpprint      = mp.print +    local mpfprint     = mp.fprint +    local mpquoted     = mp.quoted +    local jobpositions = job.positions +    local getwhd       = jobpositions.whd +    local getxy        = jobpositions.xy +    local getposition  = jobpositions.position +    local getpage      = jobpositions.page +    local getregion    = jobpositions.region +    local getmacro     = tokens.getters.macro + +    function mp.positionpath(name) +        local w, h, d = getwhd(name) +        if w then +            mpfprint("((%p,%p)--(%p,%p)--(%p,%p)--(%p,%p)--cycle)",0,-d,w,-d,w,h,0,h) +        else +            mpprint("(origin--cycle)") +        end +    end + +    function mp.positioncurve(name) +        local w, h, d = getwhd(name) +        if w then +            mpfprint("((%p,%p)..(%p,%p)..(%p,%p)..(%p,%p)..cycle)",0,-d,w,-d,w,h,0,h) +        else +            mpprint("(origin--cycle)") +        end +    end + +    function mp.positionbox(name) +        local p, x, y, w, h, d = getposition(name) +        if p then +            mpfprint("((%p,%p)--(%p,%p)--(%p,%p)--(%p,%p)--cycle)",x,y-d,x+w,y-d,x+w,y+h,x,y+h) +        else +            mpprint("(%p,%p)",x,y) +        end +    end + +    function mp.positionxy(name) +        local x, y = getxy(name) +        if x then +            mpfprint("(%p,%p)",x,y) +        else +            mpprint("origin") +        end +    end + +    function mp.positionpage(name) +        mpfprint("%i",getpage(name) or 0) +    end + +    function mp.positionregion(name) +        local r = getregion(name) +        if r then +            mpquoted(r) +        else +            mpquoted("unknown") +        end +    end + +    function mp.positionwhd(name) +        local w, h, d = getwhd(name) +        if w then +            mpfprint("(%p,%p,%p)",w,h,d) +        else +            mpprint("(0,0,0)") +        end +    end + +    function mp.positionpxy(name) +        local p, x, y = getposition(name) +        if p then +            mpfprint("(%p,%p,%p)",p,x,y) +        else +            mpprint("(0,0,0)") +        end +    end + +    function mp.positionanchor() +        mpquoted(getmacro("MPanchorid")) +    end + +end + +do + +    local mppair = mp.pair + +    function mp.textextanchor(s) +        local x, y = match(s,"tx_anchor=(%S+) (%S+)") -- todo: make an lpeg +        if x and y then +            x = tonumber(x) +            y = tonumber(y) +        end +        mppair(x or 0,y or 0) +    end + +end + +do + +    local mpprint  = mp.print +    local mpquoted = mp.quoted +    local getmacro = tokens.getters.macro + +    function mp.texvar(name) +        mpprint(getmacro(metapost.namespace .. name)) +    end + +    function mp.texstr(name) +        mpquoted(getmacro(metapost.namespace .. name)) +    end + +end + +do + +    local mpprint  = aux.print +    local mpvprint = aux.vprint + +    local hashes   = { } + +    function mp.newhash(name) +        if name then +            hashes[name] = { } +        else +            for i=1,#hashes+1 do +                if not hashes[i] then +                    hashes[i] = { } +                    mpvprint(i) +                    return +                end +            end +        end +    end + +    function mp.disposehash(n) +        if tonumber(n) then +            hashes[n] = false +        else +            hashes[n] = nil +        end +    end + +    function mp.inhash(n,key) +        local h = hashes[n] +        mpvprint(h and h[key] and true or false) +    end + +    function mp.tohash(n,key,value) +        local h = hashes[n] +        if h then +            if value == nil then +                h[key] = true +            else +                h[key] = value +            end +        end +    end + +    function mp.fromhash(n,key) +        local h = hashes[n] +        mpvprint(h and h[key] or false) +    end + +    interfaces.implement { +        name      = "MPfromhash", +        arguments = "2 strings", +        actions   = function(name,key) +            local h = hashes[name] or hashes[tonumber(name)] +            if h then +                local v = h[key] or h[tonumber(key)] +                if v then +                    context(v) +                end +            end +        end +    } + +end + +do + +    -- a bit overkill: just a find(str,"mf_object=") can be enough +    -- +    -- todo : share with mlib-pps.lua metapost,isobject + +    local mpboolean = aux.boolean + +    local p1        = P("mf_object=") +    local p2        = lpegpatterns.eol * p1 +    local pattern   = (1-p2)^0 * p2 + p1 + +    function mp.isobject(str) +        mpboolean(pattern and str ~= "" and lpegmatch(pattern,str)) +    end + +end + +function mp.flatten(t) +    local tn = #t + +    local t1 = t[1] +    local t2 = t[2] +    local t3 = t[3] +    local t4 = t[4] + +    for i=1,tn-5,2 do +        local t5 = t[i+4] +        local t6 = t[i+5] +        if t1 == t3 and t3 == t5 and ((t2 <= t4 and t4 <= t6) or (t6 <= t4 and t4 <= t2)) then +            t[i+3] = t2 +            t4     = t2 +            t[i]   = false +            t[i+1] = false +        elseif t2 == t4 and t4 == t6 and ((t1 <= t3 and t3 <= t5) or (t5 <= t3 and t3 <= t1)) then +            t[i+2] = t1 +            t3     = t1 +            t[i]   = false +            t[i+1] = false +        end +        t1 = t3 +        t2 = t4 +        t3 = t5 +        t4 = t6 +    end + +    -- remove duplicates + +    local t1 = t[1] +    local t2 = t[2] +    for i=1,tn-2,2 do +        local t3 = t[i+2] +        local t4 = t[i+3] +        if t1 == t3 and t2 == t4 then +            t[i]   = false +            t[i+1] = false +        end +        t1 = t3 +        t2 = t4 +    end + +    -- move coordinates + +    local m = 0 +    for i=1,tn,2 do +        if t[i] then +            m = m + 1 t[m] = t[i] +            m = m + 1 t[m] = t[i+1] +        end +    end + +    -- prune the table (not gc'd) + +    for i=tn,m+1,-1 do +        t[i] = nil +    end + +    -- safeguard so that we have at least one segment + +    if m == 2 then +        t[3] = t[1] +        t[4] = t[2] +    end + +end + diff --git a/tex/context/base/mkxl/mlib-pdf.lmt b/tex/context/base/mkxl/mlib-pdf.lmt new file mode 100644 index 000000000..e737b5d86 --- /dev/null +++ b/tex/context/base/mkxl/mlib-pdf.lmt @@ -0,0 +1,769 @@ +if not modules then modules = { } end modules ['mlib-pdf'] = { +    version   = 1.001, +    comment   = "companion to mlib-ctx.mkiv", +    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL", +    copyright = "PRAGMA ADE / ConTeXt Development Team", +    license   = "see context related readme files", +} + +local gsub = string.gsub +local concat, insert, remove = table.concat, table.insert, table.remove +local abs, sqrt, round = math.abs, math.sqrt, math.round +local setmetatable, rawset, tostring, tonumber, type = setmetatable, rawset, tostring, tonumber, type +local P, S, C, Ct, Cc, Cg, Cf, Carg = lpeg.P, lpeg.S, lpeg.C, lpeg.Ct, lpeg.Cc, lpeg.Cg, lpeg.Cf, lpeg.Carg +local lpegmatch = lpeg.match +local formatters = string.formatters + +local report_metapost = logs.reporter("metapost") + +local trace_variables = false  trackers.register("metapost.variables",function(v) trace_variables = v end) + +local mplib           = mplib +local context         = context + +local allocate        = utilities.storage.allocate + +local copy_node       = node.copy +local write_node      = node.write + +local pen_info        = mplib.pen_info +local getfields       = mplib.getfields or mplib.fields -- todo: in lmtx get them once and then use gettype + +local save_table      = false +local force_stroke    = false + +metapost              = metapost or { } +local metapost        = metapost + +metapost.flushers     = metapost.flushers or { } +local pdfflusher      = { } +metapost.flushers.pdf = pdfflusher + +metapost.n            = 0 + +local experiment      = true -- uses context(node) that already does delayed nodes +local savedliterals   = nil  -- needs checking +local mpsliteral      = nodes.pool.originliteral + +local f_f  = formatters["%.6N"] +local f_m  = formatters["%.6N %.6N m"] +local f_c  = formatters["%.6N %.6N %.6N %.6N %.6N %.6N c"] +local f_l  = formatters["%.6N %.6N l"] +local f_cm = formatters["%.6N %.6N %.6N %.6N %.6N %.6N cm"] +local f_M  = formatters["%.6N M"] +local f_j  = formatters["%i j"] +local f_J  = formatters["%i J"] +local f_d  = formatters["[%s] %.6N d"] +local f_w  = formatters["%.6N w"] + +directives.register("metapost.savetable",function(v) +    if type(v) == "string" then +        save_table = file.addsuffix(v,"mpl") +    elseif v then +        save_table = file.addsuffix(environment.jobname .. "-graphic","mpl") +    else +        save_table = false +    end +end) + +trackers.register("metapost.forcestroke",function(v) +    force_stroke = v +end) + +-- Because in MKiV we always have two passes, we save the objects. When an extra +-- mp run is done (due to for instance texts identifier in the parse pass), we +-- get a new result table and the stored objects are forgotten. Otherwise they +-- are reused. + +local function getobjects(result,figure,index) +    return figure:objects() +end + +function metapost.convert(specification,result) +    local flusher  = specification.flusher +    local askedfig = specification.askedfig +    if save_table then +        table.save(save_table,metapost.totable(result,1)) -- direct +    end +    metapost.flush(specification,result) +    return true -- done +end + +function metapost.flushliteral(d) +    if savedliterals then +        write_node(mpsliteral(savedliterals[d])) +    else +        report_metapost("problem flushing literal %a",d) +    end +end + +function metapost.flushreset() -- will become obsolete and internal +    savedliterals = nil +end + +function pdfflusher.comment(message) +    if message then +        message = formatters["%% mps graphic %s: %s"](metapost.n,message) +        if experiment then +            context(mpsliteral(message)) +        elseif savedliterals then +            local last = #savedliterals + 1 +            savedliterals[last] = message +            context.MPLIBtoPDF(last) +        else +            savedliterals = { message } +            context.MPLIBtoPDF(1) +        end +    end +end + +function pdfflusher.startfigure(n,llx,lly,urx,ury,message) +    savedliterals = nil +    metapost.n = metapost.n + 1 +    context.startMPLIBtoPDF(f_f(llx),f_f(lly),f_f(urx),f_f(ury)) +    if message then pdfflusher.comment(message) end +end + +function pdfflusher.stopfigure(message) +    if message then pdfflusher.comment(message) end +    context.stopMPLIBtoPDF() +    context.MPLIBflushreset() -- maybe just at the beginning +end + +function pdfflusher.flushfigure(pdfliterals) -- table +    if #pdfliterals > 0 then +        pdfliterals = concat(pdfliterals,"\n") +        if experiment then +            context(mpsliteral(pdfliterals)) +        else +            if savedliterals then +                local last = #savedliterals + 1 +                savedliterals[last] = pdfliterals +                context.MPLIBtoPDF(last) +            else +                savedliterals = { pdfliterals } +                context.MPLIBtoPDF(1) +            end +        end +    end +end + +function pdfflusher.textfigure(font,size,text,width,height,depth) -- we could save the factor +    text = gsub(text,".","\\hbox{%1}") -- kerning happens in metapost (i have to check if this is true for mplib) +    context.MPtextext(font,size,text,0,-number.dimenfactors.bp*depth) +end + +local bend_tolerance = 131/65536 + +local rx, sx, sy, ry, tx, ty, divider = 1, 0, 0, 1, 0, 0, 1 + +local function pen_characteristics(object) +    local t = pen_info(object) +    rx, ry, sx, sy, tx, ty = t.rx, t.ry, t.sx, t.sy, t.tx, t.ty +    divider = sx*sy - rx*ry +    return not (sx==1 and rx==0 and ry==0 and sy==1 and tx==0 and ty==0), t.width +end + +local function mpconcat(px, py) -- no tx, ty here / we can move this one inline if needed +    return (sy*px-ry*py)/divider,(sx*py-rx*px)/divider +end + +local function curved(ith,pth) +    local d = pth.left_x - ith.right_x +    if abs(ith.right_x - ith.x_coord - d) <= bend_tolerance and abs(pth.x_coord - pth.left_x - d) <= bend_tolerance then +        d = pth.left_y - ith.right_y +        if abs(ith.right_y - ith.y_coord - d) <= bend_tolerance and abs(pth.y_coord - pth.left_y - d) <= bend_tolerance then +            return false +        end +    end +    return true +end + +local function flushnormalpath(path, t, open) +    local pth, ith, nt +    local length = #path +    if t then +        nt = #t +    else +        t = { } +        nt = 0 +    end +    for i=1,length do +        nt = nt + 1 +        pth = path[i] +        if not ith then +            t[nt] = f_m(pth.x_coord,pth.y_coord) +        elseif curved(ith,pth) then +            t[nt] = f_c(ith.right_x,ith.right_y,pth.left_x,pth.left_y,pth.x_coord,pth.y_coord) +        else +            t[nt] = f_l(pth.x_coord,pth.y_coord) +        end +        ith = pth +    end +    if not open then +        nt = nt + 1 +        local one = path[1] +        if curved(pth,one) then +            t[nt] = f_c(pth.right_x,pth.right_y,one.left_x,one.left_y,one.x_coord,one.y_coord ) +        else +            t[nt] = f_l(one.x_coord,one.y_coord) +        end +    elseif length == 1 then +        -- special case .. draw point +        local one = path[1] +        nt = nt + 1 +        t[nt] = f_l(one.x_coord,one.y_coord) +    end +    return t +end + +local function flushconcatpath(path, t, open) +    local pth, ith, nt +    local length = #path +    if t then +        nt = #t +    else +        t = { } +        nt = 0 +    end +    nt = nt + 1 +    t[nt] = f_cm(sx,rx,ry,sy,tx,ty) +    for i=1,length do +        nt = nt + 1 +        pth = path[i] +        if not ith then +            t[nt] = f_m(mpconcat(pth.x_coord,pth.y_coord)) +        elseif curved(ith,pth) then +            local a, b = mpconcat(ith.right_x,ith.right_y) +            local c, d = mpconcat(pth.left_x,pth.left_y) +            t[nt] = f_c(a,b,c,d,mpconcat(pth.x_coord,pth.y_coord)) +        else +           t[nt] = f_l(mpconcat(pth.x_coord, pth.y_coord)) +        end +        ith = pth +    end +    if not open then +        nt = nt + 1 +        local one = path[1] +        if curved(pth,one) then +            local a, b = mpconcat(pth.right_x,pth.right_y) +            local c, d = mpconcat(one.left_x,one.left_y) +            t[nt] = f_c(a,b,c,d,mpconcat(one.x_coord, one.y_coord)) +        else +            t[nt] = f_l(mpconcat(one.x_coord,one.y_coord)) +        end +    elseif length == 1 then +        -- special case .. draw point +        nt = nt + 1 +        local one = path[1] +        t[nt] = f_l(mpconcat(one.x_coord,one.y_coord)) +    end +    return t +end + +local function toboundingbox(path) +    local size = #path +    if size == 4 then +        local pth = path[1] +        local x = pth.x_coord +        local y = pth.y_coord +        local llx, lly, urx, ury = x, y, x, y +        for i=2,size do +            pth = path[i] +            x   = pth.x_coord +            y   = pth.y_coord +            if x < llx then +                llx = x +            elseif x > urx then +                urx = x +            end +            if y < lly then +                lly = y +            elseif y > ury then +                ury = y +            end +        end +        return { llx, lly, urx, ury } +    else +        return { 0, 0, 0, 0 } +    end +end + +metapost.flushnormalpath = flushnormalpath + +-- The flusher is pdf based, if another backend is used, we need to overload the +-- flusher; this is beta code, the organization will change (already upgraded in +-- sync with mplib) +-- +-- We can avoid the before table but I like symmetry. There is of course a small +-- performance penalty, but so is passing extra arguments (result, flusher, after) +-- and returning stuff. +-- +-- This variable stuff will change in lmtx. + +local ignore   = function() end + +local space    = P(" ") +local equal    = P("=") +local key      = C((1-equal)^1) * equal +local newline  = S("\n\r")^1 +local number   = (((1-space-newline)^1) / tonumber) * (space^0) + +local p_number  = number +local p_string  = C((1-newline)^0) +local p_boolean = P("false") * Cc(false) + P("true") * Cc(true) +local p_set     = Ct(number^1) +local p_path    = Ct(Ct(number * number^-5)^1) + +local variable = +    P("1:")            * p_number +  + P("2:")            * p_string +  + P("3:")            * p_boolean +  + S("4568") * P(":") * p_set +  + P("7:")            * p_path + +local pattern_tab = Cf ( Carg(1) * (Cg(variable * newline^0)^0), rawset) + +local variable = +    P("1:")            * p_number +  + P("2:")            * p_string +  + P("3:")            * p_boolean +  + S("4568") * P(":") * number^1 +  + P("7:")            * (number * number^-5)^1 + +local pattern_lst = (variable * newline^0)^0 + +metapost.variables  = { } -- currently across instances +metapost.properties = { } -- to be stacked + +function metapost.untagvariable(str,variables) -- will be redone +    if variables == false then +        return lpegmatch(pattern_lst,str) +    else +        return lpegmatch(pattern_tab,str,1,variables or { }) +    end +end + +-- function metapost.processspecial(str) +--     lpegmatch(pattern_key,object.prescript,1,variables) +-- end + +function metapost.processspecial(str) +    local code = loadstring(str) +    if code then +        if trace_variables then +            report_metapost("executing special code: %s",str) +        end +        code() +    else +        report_metapost("invalid special code: %s",str) +    end +end + +local stack = { } + +local function pushproperties(figure) +    -- maybe there will be getters in lmtx +    local boundingbox = figure:boundingbox() +    local slot = figure:charcode() or 0 +    local properties = { +        llx    = boundingbox[1], +        lly    = boundingbox[2], +        urx    = boundingbox[3], +        ury    = boundingbox[4], +        slot   = slot, +        width  = figure:width(), +        height = figure:height(), +        depth  = figure:depth(), +        italic = figure:italcorr(), -- figure:italic() in lmtx +        number = slot, +    } +    insert(stack,properties) +    metapost.properties = properties +    return properties +end + +local function popproperties() +    metapost.properties = remove(stack) +end + +local function nocomment() end + +metapost.comment = nocomment + +function metapost.flush(specification,result) +    if result then +        local flusher   = specification.flusher +        local askedfig  = specification.askedfig +        local incontext = specification.incontext +        local figures   = result.fig +        if figures then +            flusher = flusher or pdfflusher +            local resetplugins       = metapost.resetplugins       or ignore -- before figure +            local processplugins     = metapost.processplugins     or ignore -- each object +            local synchronizeplugins = metapost.synchronizeplugins or ignore +            local pluginactions      = metapost.pluginactions      or ignore -- before / after +            local startfigure        = flusher.startfigure +            local stopfigure         = flusher.stopfigure +            local flushfigure        = flusher.flushfigure +            local textfigure         = flusher.textfigure +            local processspecial     = flusher.processspecial or metapost.processspecial +            metapost.comment         = flusher.comment or nocomment +            for index=1,#figures do +                local figure     = figures[index] +                local properties = pushproperties(figure) +                if askedfig == "direct" or askedfig == "all" or askedfig == properties.number then +                    local objects    = getobjects(result,figure,index) +                    local result     = { } +                    local miterlimit = -1 +                    local linecap    = -1 +                    local linejoin   = -1 +                    local dashed     = false +                    local linewidth  = false +                    local llx        = properties.llx +                    local lly        = properties.lly +                    local urx        = properties.urx +                    local ury        = properties.ury +                    if urx < llx then +                        -- invalid +                        startfigure(properties.number,0,0,0,0,"invalid",figure) +                        stopfigure() +                    else + +                        -- we need to be indirect if we want the one-pass solution + +                        local groupstack = { } + +                        local function processfigure() +                            result[#result+1] = "q" +                            if objects then +    --                             resetplugins(result) -- we should move the colorinitializer here +                                local savedpath = nil +                                local savedhtap = nil +                                for o=1,#objects do +                                    local object = objects[o] +                                    local objecttype = object.type +                                    if objecttype == "text" then +                                        result[#result+1] = "q" +                                        local ot = object.transform -- 3,4,5,6,1,2 +                                        result[#result+1] = f_cm(ot[3],ot[4],ot[5],ot[6],ot[1],ot[2]) +                                        flushfigure(result) -- flush accumulated literals +                                        result = { } +                                        textfigure(object.font,object.dsize,object.text,object.width,object.height,object.depth) +                                        result[#result+1] = "Q" +                                    elseif objecttype == "special" then +                                        if processspecial then +                                            processspecial(object.prescript) +                                        end +                                    elseif objecttype == "start_clip" then +                                        local evenodd = not object.istext and object.postscript == "evenodd" +                                        result[#result+1] = "q" +                                        flushnormalpath(object.path,result,false) +                                        result[#result+1] = evenodd and "W* n" or "W n" +                                    elseif objecttype == "stop_clip" then +                                        result[#result+1] = "Q" +                                        miterlimit, linecap, linejoin, dashed, linewidth = -1, -1, -1, "", false +                                    elseif objecttype == "start_bounds" or objecttype == "stop_bounds" then +                                        -- skip +                                    elseif objecttype == "start_group" then +                                        if lpdf.flushgroup then +                                            local before, after = processplugins(object) +                                            if before then +                                                result[#result+1] = "q" +                                                result = pluginactions(before,result,flushfigure) +                                                insert(groupstack, { +                                                    after  = after, +                                                    result = result, +                                                    bbox   = toboundingbox(object.path), +                                                }) +                                                result = { } +                                                miterlimit, linecap, linejoin, dashed, linewidth = -1, -1, -1, "", false +                                            else +                                                insert(groupstack,false) +                                            end +                                        else +                                            insert(groupstack,false) +                                        end +                                    elseif objecttype == "stop_group" then +                                        local data = remove(groupstack) +                                        if data then +                                            local reference = lpdf.flushgroup(concat(result,"\r"),data.bbox) +                                            result = data.result +                                            result[#result+1] = reference +                                            result = pluginactions(data.after,result,flushfigure) +                                            result[#result+1] = "Q" +                                            miterlimit, linecap, linejoin, dashed, linewidth = -1, -1, -1, "", false +                                        end +                                    else +                                        -- we use an indirect table as we want to overload +                                        -- entries but this is not possible in userdata +                                        -- +                                        -- can be optimized if no path +                                        -- +                                        local original = object +                                        local object   = { } +                                        setmetatable(object, { +                                            __index = original +                                        }) +                                        local before, +                                              after      = processplugins(object) +                                        local evenodd    = false +                                        local collect    = false +                                        local both       = false +                                        local flush      = false +                                        local postscript = object.postscript +                                        if not object.istext then +                                            if postscript == "evenodd" then +                                                evenodd = true +                                            elseif postscript == "collect" then +                                                collect = true +                                            elseif postscript == "flush" then +                                                flush   = true +                                            elseif postscript == "both" then +                                                both = true +                                            elseif postscript == "eoboth" then +                                                evenodd = true +                                                both    = true +                                            end +                                        end +                                        -- +                                        if flush and not savedpath then +                                            -- forget about it +                                        elseif collect then +                                            if not savedpath then +                                                savedpath = { object.path or false } +                                                savedhtap = { object.htap or false } +                                            else +                                                savedpath[#savedpath+1] = object.path or false +                                                savedhtap[#savedhtap+1] = object.htap or false +                                            end +                                        else +                                            local objecttype = object.type -- can have changed +                                            if before then +                                                result = pluginactions(before,result,flushfigure) +                                            end +                                            local ml = object.miterlimit +                                            if ml and ml ~= miterlimit then +                                                miterlimit = ml +                                                result[#result+1] = f_M(ml) +                                            end +                                            local lj = object.linejoin +                                            if lj and lj ~= linejoin then +                                                linejoin = lj +                                                result[#result+1] = f_j(lj) +                                            end +                                            local lc = object.linecap +                                            if lc and lc ~= linecap then +                                                linecap = lc +                                                result[#result+1] = f_J(lc) +                                            end +                                            if both then +                                                if dashed ~= false then -- was just dashed test +                                                   result[#result+1] = "[] 0 d" +                                                   dashed = false +                                                end +                                            else +                                                local dl = object.dash +                                                if dl then +                                                    local d = f_d(concat(dl.dashes or {}," "),dl.offset) +                                                    if d ~= dashed then +                                                        dashed = d +                                                        result[#result+1] = d +                                                    end +                                                elseif dashed ~= false then -- was just dashed test +                                                   result[#result+1] = "[] 0 d" +                                                   dashed = false +                                                end +                                            end +                                            local path        = object.path -- newpath +                                            local transformed = false +                                            local penwidth    = 1 +                                            local open        = path and path[1].left_type and path[#path].right_type -- at this moment only "end_point" +                                            local pen         = object.pen +                                            if pen then +                                               if pen.type == "elliptical" then +                                                    transformed, penwidth = pen_characteristics(original) -- boolean, value +                                                    if penwidth ~= linewidth then +                                                        result[#result+1] = f_w(penwidth) +                                                        linewidth = penwidth +                                                    end +                                                    if objecttype == "fill" then +                                                        objecttype = "both" +                                                    end +                                               else -- calculated by mplib itself +                                                    objecttype = "fill" +                                               end +                                            end +                                            if transformed then +                                                result[#result+1] = "q" +                                            end +                                            if path then +                                                if savedpath then +                                                    for i=1,#savedpath do +                                                        local path = savedpath[i] +                                                        if transformed then +                                                            flushconcatpath(path,result,open) +                                                        else +                                                            flushnormalpath(path,result,open) +                                                        end +                                                    end +                                                    savedpath = nil +                                                end +                                                if flush then +                                                    -- ignore this path +                                                elseif transformed then +                                                    flushconcatpath(path,result,open) +                                                else +                                                    flushnormalpath(path,result,open) +                                                end +                                                if force_stroke then +                                                    result[#result+1] = open and "S" or "h S" +                                                elseif objecttype == "fill" then +                                                    result[#result+1] = evenodd and "h f*" or "h f" -- f* = eo +                                                elseif objecttype == "outline" then +                                                    if both then +                                                        result[#result+1] = evenodd and "h B*" or "h B" -- B* = eo +                                                    else +                                                        result[#result+1] = open and "S" or "h S" +                                                    end +                                                elseif objecttype == "both" then +                                                    result[#result+1] = evenodd and "h B*" or "h B" -- B* = eo -- b includes closepath +                                                end +                                            end +                                            if transformed then +                                                result[#result+1] = "Q" +                                            end +                                            local path = object.htap +                                            if path then +                                                if transformed then +                                                    result[#result+1] = "q" +                                                end +                                                if savedhtap then +                                                    for i=1,#savedhtap do +                                                        local path = savedhtap[i] +                                                        if transformed then +                                                            flushconcatpath(path,result,open) +                                                        else +                                                            flushnormalpath(path,result,open) +                                                        end +                                                    end +                                                    savedhtap = nil +                                                    evenodd   = true +                                                end +                                                if transformed then +                                                    flushconcatpath(path,result,open) +                                                else +                                                    flushnormalpath(path,result,open) +                                                end +                                                if force_stroke then +                                                    result[#result+1] = open and "S" or "h S" +                                                elseif objecttype == "fill" then +                                                    result[#result+1] = evenodd and "h f*" or "h f" -- f* = eo +                                                elseif objecttype == "outline" then +                                                    result[#result+1] = open and "S" or "h S" +                                                elseif objecttype == "both" then +                                                    result[#result+1] = evenodd and "h B*" or "h B" -- B* = eo -- b includes closepath +                                                end +                                                if transformed then +                                                    result[#result+1] = "Q" +                                                end +                                            end +                                            if after then +                                                result = pluginactions(after,result,flushfigure) +                                            end +                                        end +                                        if object.grouped then +                                            -- can be qQ'd so changes can end up in groups +                                            miterlimit, linecap, linejoin, dashed, linewidth = -1, -1, -1, "", false +                                        end +                                    end +                                end +                            end +                            result[#result+1] = "Q" +                            flushfigure(result) +                        end +                        startfigure(properties.number,llx,lly,urx,ury,"begin",figure) +                        if incontext then +                            context(function() processfigure() end) +                        else +                            processfigure() +                        end +                        stopfigure("end") + +                    end +                    if askedfig ~= "all" then +                        break +                    end +                end +                popproperties() +            end +            metapost.comment = nocomment +            resetplugins(result) -- we should move the colorinitializer here +        end +    end +end + +-- tracing: + +do + +    local result = { } + +    local flusher = { +        startfigure = function() +            result = { } +            context.startnointerference() +        end, +        flushfigure = function(literals) +            local n = #result +            for i=1,#literals do +                result[n+i] = literals[i] +            end +        end, +        stopfigure = function() +            context.stopnointerference() +        end +    } + +    local specification = { +        flusher   = flusher, +     -- incontext = true, +    } + +    function metapost.pdfliterals(result) +        metapost.flush(specification,result) +        return result +    end + +end + +function metapost.totable(result,askedfig) +    local askedfig = askedfig or 1 +    local figure   = result and result.fig and result.fig[1] +    if figure then +        local results = { } +        local objects = getobjects(result,figure,askedfig) +        for o=1,#objects do +            local object = objects[o] +            local result = { } +            local fields = getfields(object) -- hm, is this the whole list, if so, we can get it once +            for f=1,#fields do +                local field   = fields[f] +                result[field] = object[field] +            end +            results[o] = result +        end +        local boundingbox = figure:boundingbox() +        return { +            boundingbox = { +                llx = boundingbox[1], +                lly = boundingbox[2], +                urx = boundingbox[3], +                ury = boundingbox[4], +            }, +            objects = results +        } +    else +        return nil +    end +end diff --git a/tex/context/base/mkxl/mlib-pdf.mkxl b/tex/context/base/mkxl/mlib-pdf.mkxl index 90a5b5a86..cf11e5bb7 100644 --- a/tex/context/base/mkxl/mlib-pdf.mkxl +++ b/tex/context/base/mkxl/mlib-pdf.mkxl @@ -15,7 +15,7 @@  %D We use bit more code that needed because we want to limit the amount of boxing. -\registerctxluafile{mlib-pdf}{} +\registerctxluafile{mlib-pdf}{autosuffix}  %D Some code is shared between MPLIB and MPS. The following variables are also  %D available for introspection and other purposes. diff --git a/tex/context/base/mkxl/mlib-pps.lmt b/tex/context/base/mkxl/mlib-pps.lmt new file mode 100644 index 000000000..31ef130a3 --- /dev/null +++ b/tex/context/base/mkxl/mlib-pps.lmt @@ -0,0 +1,1667 @@ +if not modules then modules = { } end modules ['mlib-pps'] = { +    version   = 1.001, +    comment   = "companion to mlib-ctx.mkiv", +    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL", +    copyright = "PRAGMA ADE / ConTeXt Development Team", +    license   = "see context related readme files", +} + +local format, gmatch, match, split, gsub = string.format, string.gmatch, string.match, string.split, string.gsub +local tonumber, type, unpack, next, select = tonumber, type, unpack, next, select +local round, sqrt, min, max = math.round, math.sqrt, math.min, math.max +local insert, remove, concat = table.insert, table.remove, table.concat +local Cs, Cf, C, Cg, Ct, P, S, V, Carg = lpeg.Cs, lpeg.Cf, lpeg.C, lpeg.Cg, lpeg.Ct, lpeg.P, lpeg.S, lpeg.V, lpeg.Carg +local lpegmatch, tsplitat, tsplitter = lpeg.match, lpeg.tsplitat, lpeg.tsplitter +local formatters = string.formatters +local exists, savedata = io.exists, io.savedata + +local mplib                = mplib +local metapost             = metapost +local lpdf                 = lpdf +local context              = context + +local scan                 = mp.scan +local inject               = mp.inject + +local mpscanstring         = scan.string +local mpscannumeric        = scan.numeric + +local injecttriplet        = inject.triplet + +local registerscript       = metapost.registerscript + +local implement            = interfaces.implement +local setmacro             = interfaces.setmacro + +local texsetbox            = tex.setbox +local textakebox           = tex.takebox -- or: nodes.takebox +local texrunlocal          = tex.runlocal +local copy_list            = node.copy_list +local flush_list           = node.flush_list +local setmetatableindex    = table.setmetatableindex +local sortedhash           = table.sortedhash + +local new_hlist            = nodes.pool.hlist + +local starttiming          = statistics.starttiming +local stoptiming           = statistics.stoptiming + +local trace_runs           = false  trackers.register("metapost.runs",     function(v) trace_runs     = v end) +local trace_textexts       = false  trackers.register("metapost.textexts", function(v) trace_textexts = v end) +local trace_scripts        = false  trackers.register("metapost.scripts",  function(v) trace_scripts  = v end) +local trace_btexetex       = false  trackers.register("metapost.btexetex", function(v) trace_btexetex = v end) + +local report_metapost      = logs.reporter("metapost") +local report_textexts      = logs.reporter("metapost","textexts") +local report_scripts       = logs.reporter("metapost","scripts") + +local colors               = attributes.colors +local defineprocesscolor   = colors.defineprocesscolor +local definespotcolor      = colors.definespotcolor +local definemultitonecolor = colors.definemultitonecolor +local colorvalue           = colors.value + +local transparencies       = attributes.transparencies +local registertransparency = transparencies.register +local transparencyvalue    = transparencies.value + +local rgbtocmyk            = colors.rgbtocmyk  -- or function() return 0,0,0,1 end +local cmyktorgb            = colors.cmyktorgb  -- or function() return 0,0,0   end +local rgbtogray            = colors.rgbtogray  -- or function() return 0       end +local cmyktogray           = colors.cmyktogray -- or function() return 0       end + +local nooutercolor         = "0 g 0 G" +local nooutertransparency  = "/Tr0 gs" -- only when set +local outercolormode       = 0 +local outercolormodel      = 1 +local outercolor           = nooutercolor +local outertransparency    = nooutertransparency +local innercolor           = nooutercolor +local innertransparency    = nooutertransparency + +local pdfcolor             = lpdf.color +local pdftransparency      = lpdf.transparency + +function metapost.setoutercolor(mode,colormodel,colorattribute,transparencyattribute) +    -- has always to be called before conversion +    -- todo: transparency (not in the mood now) +    outercolormode  = mode +    outercolormodel = colormodel +    if mode == 1 or mode == 3 then +        -- inherit from outer (registered color) +        outercolor        = pdfcolor(colormodel,colorattribute)    or nooutercolor +        outertransparency = pdftransparency(transparencyattribute) or nooutertransparency +    elseif mode == 2 then +        -- stand alone (see m-punk.tex) +        outercolor        = "" +        outertransparency = "" +    else -- 0 +        outercolor        = nooutercolor +        outertransparency = nooutertransparency +    end +    innercolor        = outercolor +    innertransparency = outertransparency -- not yet used +end + +-- todo: get this from the lpdf module + +local f_f     = formatters["%.6N"] +local f_f3    = formatters["%.3N"] +local f_gray  = formatters["%.3N g %.3N G"] +local f_rgb   = formatters["%.3N %.3N %.3N rg %.3N %.3N %.3N RG"] +local f_cmyk  = formatters["%.3N %.3N %.3N %.3N k %.3N %.3N %.3N %.3N K"] +local f_cm_b  = formatters["q %.6N %.6N %.6N %.6N %.6N %.6N cm"] +local f_scn   = formatters["%.3N"] + +local f_shade = formatters["MpSh%s"] +local f_spot  = formatters["/%s cs /%s CS %s SCN %s scn"] +local s_cm_e  = "Q" + +local function checked_color_pair(color,...) +    if not color then +        return innercolor, outercolor +    end +    if outercolormode == 3 then +        innercolor = color(...) +        return innercolor, innercolor +    else +        return color(...), outercolor +    end +end + +function metapost.colorinitializer() +    innercolor = outercolor +    innertransparency = outertransparency +    return outercolor, outertransparency +end + +--~ + +local specificationsplitter = tsplitat(" ") +local colorsplitter         = tsplitter(":",tonumber) -- no need for : +local domainsplitter        = tsplitter(" ",tonumber) +local centersplitter        = domainsplitter +local coordinatesplitter    = domainsplitter + +-- thanks to taco's reading of the postscript manual: +-- +-- x' = sx * x + ry * y + tx +-- y' = rx * x + sy * y + ty + +local nofshades = 0 -- todo: hash resources, start at 1000 in order not to clash with older + +local function normalize(ca,cb) +    if #cb == 1 then +        if #ca == 4 then +            cb[1], cb[2], cb[3], cb[4] = 0, 0, 0, 1-cb[1] +        else +            cb[1], cb[2], cb[3] = cb[1], cb[1], cb[1] +        end +    elseif #cb == 3 then +        if #ca == 4 then +            cb[1], cb[2], cb[3], cb[4] = rgbtocmyk(cb[1],cb[2],cb[3]) +        else +            cb[1], cb[2], cb[3] = cmyktorgb(cb[1],cb[2],cb[3],cb[4]) +        end +    end +end + + +local commasplitter = tsplitat(",") + +local function checkandconvertspot(n_a,f_a,c_a,v_a,n_b,f_b,c_b,v_b) +    -- must be the same but we don't check +    local name = f_shade(nofshades) +    local ca = lpegmatch(commasplitter,v_a) +    local cb = lpegmatch(commasplitter,v_b) +    if #ca == 0 or #cb == 0 then +        return { 0 }, { 1 }, "DeviceGray", name +    else +        for i=1,#ca do ca[i] = tonumber(ca[i]) or 0 end +        for i=1,#cb do cb[i] = tonumber(cb[i]) or 1 end +    --~ spotcolorconverter(n_a,f_a,c_a,v_a) -- not really needed +        return ca, cb, n_a or n_b, name +    end +end + +local function checkandconvert(ca,cb,model) +    local name = f_shade(nofshades) +    if not ca or not cb or type(ca) == "string" then +        return { 0 }, { 1 }, "DeviceGray", name +    else +        if #ca > #cb then +            normalize(ca,cb) +        elseif #ca < #cb then +            normalize(cb,ca) +        end +        if not model then +            model = colors.currentnamedmodel() +        end +        if model == "all" then +            model= (#ca == 4 and "cmyk") or (#ca == 3 and "rgb") or "gray" +        end +        if model == "rgb" then +            if #ca == 4 then +                ca = { cmyktorgb(ca[1],ca[2],ca[3],ca[4]) } +                cb = { cmyktorgb(cb[1],cb[2],cb[3],cb[4]) } +            elseif #ca == 1 then +                local a = 1 - ca[1] +                local b = 1 - cb[1] +                ca = { a, a, a } +                cb = { b, b, b } +            end +            return ca, cb, "DeviceRGB", name, model +        elseif model == "cmyk" then +            if #ca == 3 then +                ca = { rgbtocmyk(ca[1],ca[2],ca[3]) } +                cb = { rgbtocmyk(cb[1],cb[2],cb[3]) } +            elseif #ca == 1 then +                ca = { 0, 0, 0, ca[1] } +                cb = { 0, 0, 0, ca[1] } +            end +            return ca, cb, "DeviceCMYK", name, model +        else +            if #ca == 4 then +                ca = { cmyktogray(ca[1],ca[2],ca[3],ca[4]) } +                cb = { cmyktogray(cb[1],cb[2],cb[3],cb[4]) } +            elseif #ca == 3 then +                ca = { rgbtogray(ca[1],ca[2],ca[3]) } +                cb = { rgbtogray(cb[1],cb[2],cb[3]) } +            end +            -- backend specific (will be renamed) +            return ca, cb, "DeviceGray", name, model +        end +    end +end + +-- We keep textexts in a shared list (as it's easier that way and we also had that in +-- the beginning). Each graphic gets its own (1 based) subtable so that we can also +-- handle multiple conversions in one go which is needed when we process mp files +-- directly. + +local stack   = { } -- quick hack, we will pass topofstack around +local top     = nil +local nofruns = 0 -- askedfig: "all", "first", number + +local function preset(t,k) +    -- references to textexts by mp index +    local v = { +        textrial = 0, +        texfinal = 0, +        texslots = { }, +        texorder = { }, +        texhash  = { }, +    } +    t[k] = v +    return v +end + +local function startjob(plugmode,kind,mpx) +    insert(stack,top) +    top = { +        textexts   = { },                          -- all boxes, optionally with a different color +        texstrings = { }, +        texregimes = { }, +        mapstrings = { }, +        mapindices = { }, +        mapmoves   = { }, +        texlast    = 0, +        texdata    = setmetatableindex({},preset), -- references to textexts in order or usage +        plugmode   = plugmode,                     -- some day we can then skip all pre/postscripts +        extradata  = mpx and metapost.getextradata(mpx), +    } +    if trace_runs then +        report_metapost("starting %s run at level %i in %s mode", +            kind,#stack+1,plugmode and "plug" or "normal") +    end +    return top +end + +local function stopjob() +    if top then +        for slot, content in next, top.textexts do +            if content then +                flush_list(content) +                if trace_textexts then +                    report_textexts("freeing text %s",slot) +                end +            end +        end +        if trace_runs then +            report_metapost("stopping run at level %i",#stack+1) +        end +        top = remove(stack) +        return top +    end +end + +function metapost.getjobdata() +    return top +end + +-- end of new + +local settext = function(box,slot,str) +    if top then +     -- if trace_textexts then +     --     report_textexts("getting text %s from box %s",slot,box) +     -- end +        top.textexts[slot] = textakebox(box) +    end +end + +local gettext = function(box,slot) +    if top then +        texsetbox(box,top.textexts[slot]) +        top.textexts[slot] = false +     -- if trace_textexts then +     --     report_textexts("putting text %s in box %s",slot,box) +     -- end +    end +end + +metapost.settext = settext +metapost.gettext = gettext + +implement { name = "mpsettext", actions = settext, arguments = { "integer", "integer" } } -- box slot +implement { name = "mpgettext", actions = gettext, arguments = { "integer", "integer" } } -- box slot + +-- rather generic pdf, so use this elsewhere too it no longer pays +-- off to distinguish between outline and fill (we now have both +-- too, e.g. in arrows) + +metapost.reducetogray = true + +local models = { } + +function models.all(cr) +    local n = #cr +    if n == 0 then +        return checked_color_pair() +    elseif metapost.reducetogray then +        if n == 1 then +            local s = cr[1] +            return checked_color_pair(f_gray,s,s) +        elseif n == 3 then +            local r = cr[1] +            local g = cr[2] +            local b = cr[3] +            if r == g and g == b then +                return checked_color_pair(f_gray,r,r) +            else +                return checked_color_pair(f_rgb,r,g,b,r,g,b) +            end +        else +            local c = cr[1] +            local m = cr[2] +            local y = cr[3] +            local k = cr[4] +            if c == m and m == y and y == 0 then +                k = 1 - k +                return checked_color_pair(f_gray,k,k) +            else +                return checked_color_pair(f_cmyk,c,m,y,k,c,m,y,k) +            end +        end +    elseif n == 1 then +        local s = cr[1] +        return checked_color_pair(f_gray,s,s) +    elseif n == 3 then +        local r = cr[1] +        local g = cr[2] +        local b = cr[3] +        return checked_color_pair(f_rgb,r,g,b,r,g,b) +    else +        local c = cr[1] +        local m = cr[2] +        local y = cr[3] +        local k = cr[4] +        return checked_color_pair(f_cmyk,c,m,y,k,c,m,y,k) +    end +end + +function models.rgb(cr) +    local n = #cr +    if n == 0 then +        return checked_color_pair() +    elseif metapost.reducetogray then +        if n == 1 then +            local s = cr[1] +            checked_color_pair(f_gray,s,s) +        elseif n == 3 then +            local r = cr[1] +            local g = cr[2] +            local b = cr[3] +            if r == g and g == b then +                return checked_color_pair(f_gray,r,r) +            else +                return checked_color_pair(f_rgb,r,g,b,r,g,b) +            end +        else +            local c = cr[1] +            local m = cr[2] +            local y = cr[3] +            local k = cr[4] +            if c == m and m == y and y == 0 then +                k = 1 - k +                return checked_color_pair(f_gray,k,k) +            else +                local r, g, b = cmyktorgb(c,m,y,k) +                return checked_color_pair(f_rgb,r,g,b,r,g,b) +            end +        end +    elseif n == 1 then +        local s = cr[1] +        return checked_color_pair(f_gray,s,s) +    else +        local r = cr[1] +        local g = cr[2] +        local b = cr[3] +        local r, g, b +        if n == 3 then +            r, g, b = cmyktorgb(r,g,b,cr[4]) +        end +        return checked_color_pair(f_rgb,r,g,b,r,g,b) +    end +end + +function models.cmyk(cr) +    local n = #cr +    if n == 0 then +        return checked_color_pair() +    elseif metapost.reducetogray then +        if n == 1 then +            local s = cr[1] +            return checked_color_pair(f_gray,s,s) +        elseif n == 3 then +            local r = cr[1] +            local g = cr[2] +            local b = cr[3] +            if r == g and g == b then +                return checked_color_pair(f_gray,r,r) +            else +                local c, m, y, k = rgbtocmyk(r,g,b) +                return checked_color_pair(f_cmyk,c,m,y,k,c,m,y,k) +            end +        else +            local c = cr[1] +            local m = cr[2] +            local y = cr[3] +            local k = cr[4] +            if c == m and m == y and y == 0 then +                k = 1 - k +                return checked_color_pair(f_gray,k,k) +            else +                return checked_color_pair(f_cmyk,c,m,y,k,c,m,y,k) +            end +        end +    elseif n == 1 then +        local s = cr[1] +        return checked_color_pair(f_gray,s,s) +    else +        local c = cr[1] +        local m = cr[2] +        local y = cr[3] +        local k = cr[4] +        if n == 3 then +            if c == m and m == y then +                k, c, m, y = 1 - c, 0, 0, 0 +            else +                c, m, y, k = rgbtocmyk(c,m,y) +            end +        end +        return checked_color_pair(f_cmyk,c,m,y,k,c,m,y,k) +    end +end + +function models.gray(cr) +    local n = #cr +    local s = 0 +    if n == 0 then +        return checked_color_pair() +    elseif n == 4 then +        s = cmyktogray(cr[1],cr[2],cr[3],cr[4]) +    elseif n == 3 then +        s = rgbtogray(cr[1],cr[2],cr[3]) +    else +        s = cr[1] +    end +    return checked_color_pair(f_gray,s,s) +end + +models[1] = models.all +models[2] = models.gray +models[3] = models.rgb +models[4] = models.cmyk + +setmetatableindex(models, function(t,k) +    local v = models.gray +    t[k] = v +    return v +end) + +local function colorconverter(cs) +    return models[outercolormodel](cs) +end + +local factor = 65536*(7227/7200) + +implement { +    name       = "mpsetsxsy", +    arguments  = { "dimen", "dimen", "dimen" }, +    actions    = function(wd,ht,dp) +        local hd = ht + dp +        setmacro("mlib_sx",wd ~= 0 and factor/wd or 0) +        setmacro("mlib_sy",hd ~= 0 and factor/hd or 0) +    end +} + +local function sxsy(wd,ht,dp) -- helper for text +    local hd = ht + dp +    return (wd ~= 0 and factor/wd) or 0, (hd ~= 0 and factor/hd) or 0 +end + +metapost.sxsy = sxsy + +-- for stock mp we need to declare the booleans first + +local do_begin_fig = "; beginfig(1) ; " +local do_end_fig   = "; endfig ;" +local do_safeguard = ";" + +function metapost.preparetextextsdata() +    local textexts  = top.textexts +    local collected = { } +    for k, data in sortedhash(top.texdata) do -- sort is nicer in trace +        local texorder = data.texorder +        for n=1,#texorder do +            local box = textexts[texorder[n]] +            if box then +                collected[n] = box +            else +                break +            end +        end +    end +    mp.mf_tt_initialize(collected) +end + +local runmetapost = metapost.run + +local function checkaskedfig(askedfig) -- return askedfig, wrappit +    if not askedfig then +        return "direct", true +    elseif askedfig == "all" then +        return "all", false +    elseif askedfig == "direct" then +        return "all", true +    else +        askedfig = tonumber(askedfig) +        if askedfig then +            return askedfig, false +        else +            return "direct", true +        end +    end +end + +local function extrapass() +    if trace_runs then +        report_metapost("second run of job %s, asked figure %a",top.nofruns,top.askedfig) +    end +    metapost.preparetextextsdata() +    runmetapost { +        mpx         = top.mpx, +        askedfig    = top.askedfig, +        incontext   = true, +        data        = { +            top.wrappit and do_begin_fig or "", +            no_trial_run, +            top.initializations, +            do_safeguard, +            top.data, +            top.wrappit and do_end_fig or "", +        }, +    } +end + +-- This one is called from the \TEX\ end so the specification is different +-- from the specification to metapost,run cum suis! The definitions and +-- extension used to be handled here but are now delegated to the format +-- initializers because we need to accumulate them for nested instances (a +-- side effect of going single pass). + +function metapost.graphic_base_pass(specification) +    local mpx             = specification.mpx -- mandate +    local top             = startjob(true,"base",mpx) +    local data            = specification.data or "" +    local inclusions      = specification.inclusions or "" +    local initializations = specification.initializations or "" +    local askedfig, +          wrappit         = checkaskedfig(specification.figure) +    nofruns               = nofruns + 1 +    top.askedfig          = askedfig +    top.wrappit           = wrappit +    top.nofruns           = nofruns +    metapost.namespace    = specification.namespace or "" +    top.mpx               = mpx +    top.data              = data +    top.initializations   = initializations +    if trace_runs then +        report_metapost("running job %s, asked figure %a",nofruns,askedfig) +    end +    runmetapost { +        mpx         = mpx, +        askedfig    = askedfig, +        incontext   = true, +        data        = { +            inclusions, +            wrappit and do_begin_fig or "", +            initializations, +            do_safeguard, +            data, +            wrappit and do_end_fig or "", +        }, +    } +    context(stopjob) +end + +local function oldschool(mpx, data, trial_run, flusher, was_multi_pass, is_extra_pass, askedfig, incontext) +    metapost.process { +        mpx        = mpx, +        flusher    = flusher, +        askedfig   = askedfig, +        useplugins = incontext, +        incontext  = incontext, +        data       = data, +    } +end + +function metapost.process(specification,...) +    if type(specification) ~= "table" then +        oldschool(specification,...) +    else +        startjob(specification.incontext or specification.useplugins,"process",false) +        runmetapost(specification) +        stopjob() +    end +end + +-- -- the new plugin handler -- -- + +local sequencers       = utilities.sequencers +local appendgroup      = sequencers.appendgroup +local appendaction     = sequencers.appendaction + +local resetteractions  = sequencers.new { arguments = "t" } +local processoractions = sequencers.new { arguments = "object,prescript,before,after" } + +appendgroup(resetteractions, "system") +appendgroup(processoractions,"system") + +-- later entries come first + +local scriptsplitter = Ct ( Ct ( +    C((1-S("= "))^1) * S("= ")^1 * C((1-S("\n\r"))^0) * S("\n\r")^0 +)^0 ) + +local function splitprescript(script) +    local hash = lpegmatch(scriptsplitter,script) +    for i=#hash,1,-1 do +        local h = hash[i] +        if h == "reset" then +            for k, v in next, hash do +                if type(k) ~= "number" then +                    hash[k] = nil +                end +            end +        else +            hash[h[1]] = h[2] +        end +    end +    if trace_scripts then +        report_scripts(table.serialize(hash,"prescript")) +    end +    return hash +end + +metapost.splitprescript = splitprescript + +-- -- not used: +-- +-- local function splitpostscript(script) +--     local hash = lpegmatch(scriptsplitter,script) +--     for i=1,#hash do +--         local h = hash[i] +--         hash[h[1]] = h[2] +--     end +--     if trace_scripts then +--         report_scripts(table.serialize(hash,"postscript")) +--     end +--     return hash +-- end + +function metapost.pluginactions(what,t,flushfigure) -- before/after object, depending on what +    if top and top.plugmode then -- hm, what about other features +        for i=1,#what do +            local wi = what[i] +            if type(wi) == "function" then +                -- assume injection +                flushfigure(t) -- to be checked: too many 0 g 0 G +                t = { } +                wi() +            else +                t[#t+1] = wi +            end +        end +        return t +    end +end + +function metapost.resetplugins(t) -- intialize plugins, before figure +    if top and top.plugmode then +        outercolormodel = colors.currentmodel() -- currently overloads the one set at the tex end +        resetteractions.runner(t) +    end +end + +function metapost.processplugins(object) -- each object (second pass) +    if top and top.plugmode then +        local prescript = object.prescript   -- specifications +        if prescript and #prescript > 0 then +            local before = { } +            local after = { } +            processoractions.runner(object,splitprescript(prescript) or { },before,after) +            return #before > 0 and before, #after > 0 and after +        else +            local c = object.color +            if c and #c > 0 then +                local b, a = colorconverter(c) +                return { b }, { a } +            end +        end +    end +end + +-- helpers + +local basepoints = number.dimenfactors["bp"] + +local function cm(object) +    local op = object.path +    if op then +        local first  = op[1] +        local second = op[2] +        local fourth = op[4] +        if fourth then +            local tx = first.x_coord +            local ty = first.y_coord +            local sx = second.x_coord - tx +            local sy = fourth.y_coord - ty +            local rx = second.y_coord - ty +            local ry = fourth.x_coord - tx +            if sx == 0 then sx = 0.00001 end +            if sy == 0 then sy = 0.00001 end +            return sx, rx, ry, sy, tx, ty +        end +    end +    return 1, 0, 0, 1, 0, 0 -- weird case +end + +metapost.cm = cm + +-- color + +local function cl_reset(t) +    t[#t+1] = metapost.colorinitializer() -- only color +end + +-- text + +local tx_reset, tx_process  do + +    local eol      = S("\n\r")^1 +    local cleaner  = Cs((P("@@")/"@" + P("@")/"%%" + P(1))^0) +    local splitter = Ct( +        ( ( +            P("s:") * C((1-eol)^1) +          + P("n:") *  ((1-eol)^1/tonumber) +          + P("b:") *  ((1-eol)^1/toboolean) +        ) * eol^0 )^0) + +    local function applyformat(s) +        local t = lpegmatch(splitter,s) +        if #t == 1 then +            return s +        else +            local f = lpegmatch(cleaner,t[1]) +            return formatters[f](unpack(t,2)) +        end +    end + +    local fmt = formatters["%s %s %s % t"] +    ----- pat = tsplitat(":") +    local pat = lpeg.tsplitter(":",tonumber) -- so that %F can do its work + +    local f_gray_yes = formatters["s=%.3N,a=%i,t=%.3N"] +    local f_gray_nop = formatters["s=%.3N"] +    local f_rgb_yes  = formatters["r=%.3N,g=%.3N,b=%.3N,a=%.3N,t=%.3N"] +    local f_rgb_nop  = formatters["r=%.3N,g=%.3N,b=%.3N"] +    local f_cmyk_yes = formatters["c=%.3N,m=%.3N,y=%.3N,k=%.3N,a=%.3N,t=%.3N"] +    local f_cmyk_nop = formatters["c=%.3N,m=%.3N,y=%.3N,k=%.3N"] + +    local ctx_MPLIBsetNtext = context.MPLIBsetNtextX +    local ctx_MPLIBsetCtext = context.MPLIBsetCtextX +    local ctx_MPLIBsettext  = context.MPLIBsettextX + +    local bp = number.dimenfactors.bp + +    local mp_index  = 0 +    local mp_target = 0 +    local mp_c      = nil +    local mp_a      = nil +    local mp_t      = nil + +    local function processtext() +        local mp_text   = top.texstrings[mp_index] +        local mp_regime = top.texregimes[mp_index] +        if mp_regime and tonumber(mp_regime) >= 0 then +            mp_text = function() +                context.sprint(mp_regime,top.texstrings[mp_index] or "") +            end +        end +        if not mp_text then +            report_textexts("missing text for index %a",mp_index) +        elseif not mp_c then +            ctx_MPLIBsetNtext(mp_target,mp_text) +        elseif #mp_c == 1 then +            if mp_a and mp_t then +                ctx_MPLIBsetCtext(mp_target,f_gray_yes(mp_c[1],mp_a,mp_t),mp_text) +            else +                ctx_MPLIBsetCtext(mp_target,f_gray_nop(mp_c[1]),mp_text) +            end +        elseif #mp_c == 3 then +            if mp_a and mp_t then +                ctx_MPLIBsetCtext(mp_target,f_rgb_yes(mp_c[1],mp_c[2],mp_c[3],mp_a,mp_t),mp_text) +            else +                ctx_MPLIBsetCtext(mp_target,f_rgb_nop(mp_c[1],mp_c[2],mp_c[3]),mp_text) +            end +        elseif #mp_c == 4 then +            if mp_a and mp_t then +                ctx_MPLIBsetCtext(mp_target,f_cmyk_yes(mp_c[1],mp_c[2],mp_c[3],mp_c[4],mp_a,mp_t),mp_text) +            else +                ctx_MPLIBsetCtext(mp_target,f_cmyk_nop(mp_c[1],mp_c[2],mp_c[3],mp_c[4]),mp_text) +            end +        else +            -- can't happen +            ctx_MPLIBsetNtext(mp_target,mp_text) +        end +    end + +    local madetext = nil + +    local function mf_some_text(index,str,regime) +        mp_target = index +        mp_index  = index +        mp_c      = nil +        mp_a      = nil +        mp_t      = nil +        top.texstrings[mp_index] = str +        top.texregimes[mp_index] = regime or -1 +        texrunlocal("mptexttoks") +        local box = textakebox("mptextbox") +        top.textexts[mp_target] = box +        injecttriplet(bp*box.width,bp*box.height,bp*box.depth) +        madetext = nil +    end + +    local function mf_made_text(index) +        mf_some_text(index,madetext,catcodes.numbers.ctxcatcodes) -- btex/etex .. +    end + +    registerscript("sometextext", function() mf_some_text(mpscannumeric(),mpscanstring(),mpscannumeric()) end) +    registerscript("madetextext", function() mf_made_text(mpscannumeric()) end) + +    -- a label can be anything, also something mp doesn't like in strings +    -- so we return an index instead + +    function metapost.processing() +        return top and true or false +    end + +    function metapost.remaptext(replacement) +        if top then +            local mapstrings = top.mapstrings +            local mapindices = top.mapindices +            local label      = replacement.label +            local index      = 0 +            if label then +                local found = mapstrings[label] +                if found then +                    setmetatableindex(found,replacement) +                    index = found.index +                else +                    index = #mapindices + 1 +                    replacement.index = index +                    mapindices[index] = replacement +                    mapstrings[label] = replacement +                end +            end +            return index +        else +            return 0 +        end +    end + +    function metapost.remappedtext(what) +        return top and (top.mapstrings[what] or top.mapindices[tonumber(what)]) +    end + +    function mp.mf_map_move(index) +        mp.triplet(top.mapmoves[index]) +    end + +    function mp.mf_map_text(index,str,regime) +        local map = top.mapindices[tonumber(str)] +        if type(map) == "table" then +            local text     = map.text +            local overload = map.overload +            local offset   = 0 +            local width    = 0 +            local where    = nil +            -- +            mp_index = index +            -- the image text +            if overload then +                top.texstrings[mp_index] = map.template or map.label or "error" +                top.texregimes[mp_index] = regime or -1 +                texrunlocal("mptexttoks") +                local box = textakebox("mptextbox") or new_hlist() +                width = bp * box.width +                where = overload.where +            end +            -- the real text +            top.texstrings[mp_index] = overload and overload.text or text or "error" +            top.texregimes[mp_index] = regime or -1 +            texrunlocal("mptexttoks") +            local box = textakebox("mptextbox") or new_hlist() +            local twd = bp * box.width +            local tht = bp * box.height +            local tdp = bp * box.depth +            -- the check +            if where then +                local scale = 1 --  / (map.scale or 1) +                if where == "l" or where == "left" then +                    offset = scale * (twd - width) +                elseif where == "m" or where == "middle" then +                    offset = scale * (twd - width) / 2 +                end +            end +            -- the result +            top.textexts[mp_index] = box +            top.mapmoves[mp_index] = { offset, map.dx or 0, map.dy or 0 } +            -- +            mp.triplet(twd,tht,tdp) +            madetext = nil +            return +        else +            map = type(map) == "string" and map or str +            return mf_some_text(index,context.escape(map) or map) +        end +    end + +    -- This is a bit messy. In regular metapost it's a kind of immediate replacement +    -- so embedded btex ... etex is not really working as one would expect. We now have +    -- a mix: it's immediate when we are at the outer level (rawmadetext) and indirect +    -- (with the danger of stuff that doesn't work well in strings) when we are for +    -- instance in a macro definition (rawtextext (pass back string)) ... of course one +    -- should use textext so this is just a catch. When not in lmtx it's never immediate. + +    local reported   = false +    local alwayswrap = false -- CONTEXTLMTXMODE <= 1 -- was always nil due to typo + +    function metapost.maketext(s,mode) +        if not reported then +            reported = true +            report_metapost("use 'textext(.....)' instead of 'btex ..... etex'") +        end +        if mode and mode == 1 then +            if trace_btexetex then +                report_metapost("ignoring verbatimtex: [[%s]]",s) +            end +        elseif alwayswrap then +            if trace_btexetex then +                report_metapost("rewrapping btex ... etex [[%s]]",s) +            end +            return 'rawtextext("' .. gsub(s,'"','"&ditto&"') .. '")' -- nullpicture +        elseif metapost.currentmpxstatus() ~= 0 then +            if trace_btexetex then +                report_metapost("rewrapping btex ... etex at the outer level [[%s]]",s) +            end +            return 'rawtextext("' .. gsub(s,'"','"&ditto&"') .. '")' -- nullpicture +        else +            if trace_btexetex then +                report_metapost("handling btex ... etex: [[%s]]",s) +            end +         -- madetext = utilities.strings.collapse(s) +            madetext = s +            return "rawmadetext" -- is assuming immediate processing +        end +    end + +    function mp.mf_formatted_text(index,fmt,...) +        local t = { } +        for i=1,select("#",...) do +            local ti = select(i,...) +            if type(ti) ~= "table" then +                t[#t+1] = ti +            end +        end +        local f = lpegmatch(cleaner,fmt) +        local s = formatters[f](unpack(t)) or "" +        mf_some_text(index,s) +    end + +    interfaces.implement { +        name    = "mptexttoks", +        actions = processtext, +    } + +    tx_reset = function() +        if top then +            top.texhash = { } +            top.texlast = 0 +        end +    end + +    tx_process = function(object,prescript,before,after) +        local data  = top.texdata[metapost.properties.number] -- the current figure number, messy +        local index = tonumber(prescript.tx_index) +        if index then +            if trace_textexts then +                report_textexts("using index %a",index) +            end +            -- +            mp_c = object.color +            if #mp_c == 0 then +                local txc = prescript.tx_color +                if txc then +                    mp_c = lpegmatch(pat,txc) +                end +            end +            mp_a = tonumber(prescript.tr_alternative) +            mp_t = tonumber(prescript.tr_transparency) +            -- +            mp_index  = index +            mp_target = top.texlast - 1 +            top.texlast = mp_target +            -- +            local mp_text = top.texstrings[mp_index] +            local mp_hash = prescript.tx_cache +            local box +            if mp_hash == "no" then +                texrunlocal("mptexttoks") +                box = textakebox("mptextbox") +            else +                local cache = data.texhash +                if mp_hash then +                    mp_hash = tonumber(mp_hash) +                end +                if mp_hash then +                    local extradata = top.extradata +                    if extradata then +                        cache = extradata.globalcache +                        if not cache then +                            cache = { } +                            extradata.globalcache = cache +                        end +                        if trace_runs then +                            if cache[mp_hash] then +                                report_textexts("reusing global entry %i",mp_hash) +                            else +                                report_textexts("storing global entry %i",mp_hash) +                            end +                        end +                    else +                        mp_hash = nil +                    end +                end +                if not mp_hash then +                    mp_hash = fmt(mp_text,mp_a or "-",mp_t or "-",mp_c or "-") +                end +                box = cache[mp_hash] +                if box then +                    box = copy_list(box) +                else +                    texrunlocal("mptexttoks") +                    box = textakebox("mptextbox") +                    cache[mp_hash] = box +                end +            end +            top.textexts[mp_target] = box +            -- +            if box then +                -- we need to freeze the variables outside the function +                local sx, rx, ry, sy, tx, ty = cm(object) +                local target = mp_target +                before[#before+1] = function() +                    context.MPLIBgettextscaledcm(target, +                        f_f(sx), -- bah ... %s no longer checks +                        f_f(rx), -- bah ... %s no longer checks +                        f_f(ry), -- bah ... %s no longer checks +                        f_f(sy), -- bah ... %s no longer checks +                        f_f(tx), -- bah ... %s no longer checks +                        f_f(ty), -- bah ... %s no longer checks +                        sxsy(box.width,box.height,box.depth)) +                end +            else +                before[#before+1] = function() +                    report_textexts("unknown %s",index) +                end +            end +            if not trace_textexts then +                object.path = false -- else: keep it +            end +            object.color   = false +            object.grouped = true +            object.istext  = true +        end +    end + +end + +-- we could probably redo normal textexts in the next way but as it's rather optimized +-- we keep away from that (at least for now) + +local function bx_process(object,prescript,before,after) +    local bx_category = prescript.bx_category +    local bx_name     = prescript.bx_name +    if bx_category and bx_name then +        if trace_textexts then +            report_textexts("category %a, name %a",bx_category,bx_name) +        end +        local sx, rx, ry, sy, tx, ty = cm(object) -- needs to be frozen outside the function +        local wd, ht, dp = nodes.boxes.dimensions(bx_category,bx_name) +        before[#before+1] = function() +            context.MPLIBgetboxscaledcm(bx_category,bx_name, +                f_f(sx), -- bah ... %s no longer checks +                f_f(rx), -- bah ... %s no longer checks +                f_f(ry), -- bah ... %s no longer checks +                f_f(sy), -- bah ... %s no longer checks +                f_f(tx), -- bah ... %s no longer checks +                f_f(ty), -- bah ... %s no longer checks +                sxsy(wd,ht,dp)) +        end +        if not trace_textexts then +            object.path = false -- else: keep it +        end +        object.color   = false +        object.grouped = true +        object.istext  = true +    end +end + +-- graphics (we use the given index because pictures can be reused) + + +local gt_reset, gt_process do + +    local graphics = { } + + +    local mp_index = 0 +    local mp_str   = "" + +    function mp.mf_graphic_text(index,str) +        if not graphics[index] then +            mp_index = index +            mp_str   = str +            texrunlocal("mpgraphictexttoks") +        end +    end + +    interfaces.implement { +        name    = "mpgraphictexttoks", +        actions = function() +            context.MPLIBgraphictext(mp_index,mp_str) +        end, +    } + +end + +-- shades + +local function sh_process(object,prescript,before,after) +    local sh_type = prescript.sh_type +    if sh_type then +        nofshades = nofshades + 1 +        local domain    = lpegmatch(domainsplitter,prescript.sh_domain   or "0 1") +        local centera   = lpegmatch(centersplitter,prescript.sh_center_a or "0 0") +        local centerb   = lpegmatch(centersplitter,prescript.sh_center_b or "0 0") +        local transform = toboolean(prescript.sh_transform or "yes",true) +        -- compensation for scaling +        local sx = 1 +        local sy = 1 +        local sr = 1 +        local dx = 0 +        local dy = 0 +        if transform then +            local first = lpegmatch(coordinatesplitter,prescript.sh_first or "0 0") +            local setx  = lpegmatch(coordinatesplitter,prescript.sh_set_x or "0 0") +            local sety  = lpegmatch(coordinatesplitter,prescript.sh_set_y or "0 0") + +            local x = setx[1] -- point that has different x +            local y = sety[1] -- point that has different y + +            if x == 0 or y == 0 then +                -- forget about it +            else +                local path   = object.path +                local path1x = path[1].x_coord +                local path1y = path[1].y_coord +                local path2x = path[x].x_coord +                local path2y = path[y].y_coord + +                local dxa = path2x - path1x +                local dya = path2y - path1y +                local dxb = setx[2] - first[1] +                local dyb = sety[2] - first[2] + +                if dxa == 0 or dya == 0 or dxb == 0 or dyb == 0 then +                    -- forget about it +                else +                    sx = dxa / dxb ; if sx < 0 then sx = - sx end -- yes or no +                    sy = dya / dyb ; if sy < 0 then sy = - sy end -- yes or no + +                    sr = sqrt(sx^2 + sy^2) + +                    dx = path1x - sx*first[1] +                    dy = path1y - sy*first[2] +                end +            end +        end + +        local steps      = tonumber(prescript.sh_step) or 1 +        local sh_color_a = prescript.sh_color_a_1 or prescript.sh_color_a or "1" +        local sh_color_b = prescript.sh_color_b_1 or prescript.sh_color_b or "1" -- sh_color_b_<sh_steps> +        local ca, cb, colorspace, name, model, separation, fractions +        if prescript.sh_color == "into" and prescript.sp_name then +            -- some spotcolor +            local value_a, components_a, fractions_a, name_a +            local value_b, components_b, fractions_b, name_b +            for i=1,#prescript do +                -- { "sh_color_a", "1" }, +                -- { "sh_color", "into" }, +                -- { "sh_radius_b", "0" }, +                -- { "sh_radius_a", "141.73225" }, +                -- { "sh_center_b", "425.19676 141.73225" }, +                -- { "sh_center_a", "425.19676 0" }, +                -- { "sh_factor", "1" }, +                local tag = prescript[i][1] +                if not name_a and tag == "sh_color_a" then +                    value_a      = prescript[i-5][2] +                    components_a = prescript[i-4][2] +                    fractions_a  = prescript[i-3][2] +                    name_a       = prescript[i-2][2] +                elseif not name_b and tag == "sh_color_b" then +                    value_b      = prescript[i-5][2] +                    components_b = prescript[i-4][2] +                    fractions_b  = prescript[i-3][2] +                    name_b       = prescript[i-2][2] +                end +                if name_a and name_b then +                    break +                end +            end +            ca, cb, separation, name = checkandconvertspot( +                name_a,fractions_a,components_a,value_a, +                name_b,fractions_b,components_b,value_b +            ) +        else +            local colora = lpegmatch(colorsplitter,sh_color_a) +            local colorb = lpegmatch(colorsplitter,sh_color_b) +            ca, cb, colorspace, name, model = checkandconvert(colora,colorb) +            -- test: +            if steps > 1 then +                ca = { ca } +                cb = { cb } +                fractions = { tonumber(prescript[formatters["sh_fraction_%i"](1)]) or 0 } +                for i=2,steps do +                    local colora = lpegmatch(colorsplitter,prescript[formatters["sh_color_a_%i"](i)]) +                    local colorb = lpegmatch(colorsplitter,prescript[formatters["sh_color_b_%i"](i)]) +                    ca[i], cb[i] = checkandconvert(colora,colorb,model) +                    fractions[i] = tonumber(prescript[formatters["sh_fraction_%i"](i)]) or (i/steps) +                end +            end +        end +        if not ca or not cb then +            ca, cb, colorspace, name = checkandconvert() +            steps = 1 +        end +        if sh_type == "linear" then +            local coordinates = { dx + sx*centera[1], dy + sy*centera[2], dx + sx*centerb[1], dy + sy*centerb[2] } +            lpdf.linearshade(name,domain,ca,cb,1,colorspace,coordinates,separation,steps>1 and steps,fractions) -- backend specific (will be renamed) +        elseif sh_type == "circular" then +            local factor  = tonumber(prescript.sh_factor) or 1 +            local radiusa = factor * tonumber(prescript.sh_radius_a) +            local radiusb = factor * tonumber(prescript.sh_radius_b) +            local coordinates = { dx + sx*centera[1], dy + sy*centera[2], sr*radiusa, dx + sx*centerb[1], dy + sy*centerb[2], sr*radiusb } +            lpdf.circularshade(name,domain,ca,cb,1,colorspace,coordinates,separation,steps>1 and steps,fractions) -- backend specific (will be renamed) +        else +            -- fatal error +        end +        before[#before+1] = "q /Pattern cs" +        after [#after+1]  = formatters["W n /%s sh Q"](name) +        -- false, not nil, else mt triggered +        object.colored = false -- hm, not object.color ? +        object.type    = false +        object.grouped = true +    end +end + +-- bitmaps + +local function bm_process(object,prescript,before,after) +    local bm_xresolution = prescript.bm_xresolution +    if bm_xresolution then +        before[#before+1] = f_cm_b(cm(object)) +        before[#before+1] = function() +            figures.bitmapimage { +                xresolution = tonumber(bm_xresolution), +                yresolution = tonumber(prescript.bm_yresolution), +                width       = 1/basepoints, +                height      = 1/basepoints, +                data        = object.postscript +            } +        end +        before[#before+1] = s_cm_e +        object.path = false +        object.color = false +        object.grouped = true +    end +end + +-- positions + +local function ps_process(object,prescript,before,after) +    local ps_label = prescript.ps_label +    if ps_label then +        local op     = object.path +        local first  = op[1] +        local third  = op[3] +        local x      = first.x_coord +        local y      = first.y_coord +        local w      = third.x_coord - x +        local h      = third.y_coord - y +        local properties = metapost.properties +        x = x - properties.llx +        y = properties.ury - y +        before[#before+1] = function() +            context.MPLIBpositionwhd(ps_label,x,y,w,h) +        end +        object.path = false +    end +end + +-- figures + +-- local sx, rx, ry, sy, tx, ty = cm(object) +-- sxsy(box.width,box.height,box.depth)) + +function mp.mf_external_figure(filename) +    local f = figures.getinfo(filename) +    local w = 0 +    local h = 0 +    if f then +        local u = f.used +        if u and u.fullname then +            w = u.width or 0 +            h = u.height or 0 +        end +    else +        report_metapost("external figure %a not found",filename) +    end +    mp.triplet(w/65536,h/65536,0) +end + +local function fg_process(object,prescript,before,after) +    local fg_name = prescript.fg_name +    if fg_name then +        before[#before+1] = f_cm_b(cm(object)) -- beware: does not use the cm stack +        before[#before+1] = function() +            context.MPLIBfigure(fg_name,prescript.fg_mask or "") +        end +        before[#before+1] = s_cm_e +        object.path = false +        object.grouped = true +    end +end + +-- color and transparency + +local value = Cs ( ( +    (Carg(1) * C((1-P(","))^1)) / function(a,b) return f_f3(a * tonumber(b)) end +  + P(","))^1 +) + +-- should be codeinjections + +local t_list = attributes.list[attributes.private('transparency')] +local c_list = attributes.list[attributes.private('color')] + +local remappers = { +    [1] = formatters["s=%s"], +    [3] = formatters["r=%s,g=%s,b=%s"], +    [4] = formatters["c=%s,m=%s,y=%s,k=%s"], +} + +local processlast = 0 +local processhash = setmetatableindex(function(t,k) +    processlast = processlast + 1 +    local v = formatters["mp_%s"](processlast) +    defineprocesscolor(v,k,true,true) +    t[k] = v +    return v +end) + +local function checked_transparency(alternative,transparency,before,after) +    alternative  = tonumber(alternative)  or 1 +    transparency = tonumber(transparency) or 0 +    before[#before+1] = formatters["/Tr%s gs"](registertransparency(nil,alternative,transparency,true)) +    after [#after +1] = "/Tr0 gs" -- outertransparency +end + +local function tr_process(object,prescript,before,after) +    -- before can be shortcut to t +    local tr_alternative = prescript.tr_alternative +    if tr_alternative then +        checked_transparency(tr_alternative,prescript.tr_transparency,before,after) +    end +    local cs = object.color +    if cs and #cs > 0 then +        local c_b, c_a +        local sp_type = prescript.sp_type +        if not sp_type then +            c_b, c_a = colorconverter(cs) +        else +            local sp_name = prescript.sp_name or "black" +            if sp_type == "spot" then +                local sp_value      = prescript.sp_value or "1" +                local components    = split(sp_value,":") +                local specification = remappers[#components] +                if specification then +                    specification = specification(unpack(components)) +                else +                    specification = "s=0" +                end +                local sp_spec = processhash[specification] +                definespotcolor(sp_name,sp_spec,"p=1",true) +                sp_type = "named" +            elseif sp_type == "multitone" then -- (fractions of a multitone) don't work well in mupdf +                local sp_value = prescript.sp_value or "1" +                local sp_specs = { } +                local sp_list  = split(sp_value," ") +                for i=1,#sp_list do +                    local sp_value      = sp_list[i] +                    local components    = split(sp_value,":") +                    local specification = remappers[#components] +                    if specification then +                        specification = specification(unpack(components)) +                    else +                        specification = "s=0" +                    end +                    local sp_spec = processhash[specification] +                    sp_specs[i] = formatters["%s=1"](sp_spec) +                end +                sp_specs = concat(sp_specs,",") +                definemultitonecolor(sp_name,sp_specs,"","") +                sp_type = "named" +            end +            if sp_type == "named" then +                -- we might move this to another namespace .. also, named can be a spotcolor +                -- so we need to check for that too ... also we need to resolve indirect +                -- colors so we might need the second pass for this (draw dots with \MPcolor) +                if not tr_alternative then +                    -- todo: sp_name is not yet registered at this time +                    local t = t_list[sp_name] -- string or attribute +                    local v = t and transparencyvalue(t) +                    if v then +                        checked_transparency(v[1],v[2],before,after) +                    end +                end +                local c = c_list[sp_name] -- string or attribute +                local v = c and colorvalue(c) +                if v then +                    -- all=1 gray=2 rgb=3 cmyk=4 +                    local colorspace = v[1] +                    local factor     = cs[1] +                    if colorspace == 2 then +                        local s = factor * v[2] +                        c_b, c_a = checked_color_pair(f_gray,s,s) +                    elseif colorspace == 3 then +                        local r = factor * v[3] +                        local g = factor * v[4] +                        local b = factor * v[5] +                        c_b, c_a = checked_color_pair(f_rgb,r,g,b,r,g,b) +                    elseif colorspace == 4 or colorspace == 1 then +                        local c = factor * v[6] +                        local m = factor * v[7] +                        local y = factor * v[8] +                        local k = factor * v[9] +                        c_b, c_a = checked_color_pair(f_cmyk,c,m,y,k,c,m,y,k) +                    elseif colorspace == 5 then +                        -- not all viewers show the fractions ok +                        local name  = v[10] +                        local value = split(v[13],",") +                        if factor ~= 1 then +                            for i=1,#value do +                                value[i] = f_scn(factor * (tonumber(value[i]) or 1)) +                            end +                        end +                        value = concat(value," ") +                        c_b, c_a = checked_color_pair(f_spot,name,name,value,value) +                    else +                        local s = factor *v[2] +                        c_b, c_a = checked_color_pair(f_gray,s,s) +                    end +                end +            end +        end +        if c_a and c_b then +            before[#before+1] = c_b +            after [#after +1] = c_a +        end +    end +end + +-- layers (nasty: we need to keep the 'grouping' right + +local function la_process(object,prescript,before,after) +    local la_name = prescript.la_name +    if la_name then +        before[#before+1] = backends.codeinjections.startlayer(la_name) +        insert(after,1,backends.codeinjections.stoplayer()) +    end +end + +-- groups + +local function gr_process(object,prescript,before,after) +    local gr_state = prescript.gr_state +    if not gr_state then +       return +    elseif gr_state == "start" then +        local gr_type = utilities.parsers.settings_to_set(prescript.gr_type) +        local path = object.path +        local p1 = path[1] +        local p2 = path[2] +        local p3 = path[3] +        local p4 = path[4] +        local llx = min(p1.x_coord,p2.x_coord,p3.x_coord,p4.x_coord) +        local lly = min(p1.y_coord,p2.y_coord,p3.y_coord,p4.y_coord) +        local urx = max(p1.x_coord,p2.x_coord,p3.x_coord,p4.x_coord) +        local ury = max(p1.y_coord,p2.y_coord,p3.y_coord,p4.y_coord) +        before[#before+1] = function() +            context.MPLIBstartgroup( +                gr_type.isolated and 1 or 0, +                gr_type.knockout and 1 or 0, +                llx, lly, urx, ury +            ) +        end +    elseif gr_state == "stop" then +        after[#after+1] = function() +            context.MPLIBstopgroup() +        end +    end +    object.path    = false +    object.color   = false +    object.grouped = true +end + +-- outlines + +local ot_reset, ot_process do + +    local outlinetexts = { } -- also in top data + +    ot_reset = function () +        outlinetexts = { } +    end + +    local mp_index = 0 +    local mp_kind  = "" +    local mp_str   = "" + +    function mp.mf_outline_text(index,str,kind) +        if not outlinetexts[index] then +            mp_index = index +            mp_kind  = kind +            mp_str   = str +            texrunlocal("mpoutlinetoks") +        end +    end + +    interfaces.implement { +        name    = "mpoutlinetoks", +        actions = function() +            context.MPLIBoutlinetext(mp_index,mp_kind,mp_str) +        end, +    } + +    implement { +        name      = "MPLIBconvertoutlinetext", +        arguments = { "integer", "string", "integer" }, +        actions   = function(index,kind,box) +            local boxtomp = fonts.metapost.boxtomp +            if boxtomp then +                outlinetexts[index] = boxtomp(box,kind) +            else +                outlinetexts[index] = "" +            end +        end +    } + +    function mp.mf_get_outline_text(index) -- maybe we need a more private namespace +        mp.print(outlinetexts[index] or "draw origin;") +    end + +end + +-- mf_object=<string> + +local p1      = P("mf_object=") +local p2      = lpeg.patterns.eol * p1 +local pattern = (1-p2)^0 * p2 + p1 + +function metapost.isobject(str) +    return pattern and str ~= "" and lpegmatch(p,str) and true or false +end + +local function installplugin(specification) +    local reset   = specification.reset +    local process = specification.process +    local object  = specification.object +    if reset then +        appendaction(resetteractions,"system",reset) +    end +    if process then +        appendaction(processoractions,"system",process) +    end +end + +metapost.installplugin = installplugin + +-- definitions + +installplugin { name = "outline",      reset = ot_reset, process = ot_process } +installplugin { name = "color",        reset = cl_reset, process = cl_process } +installplugin { name = "text",         reset = tx_reset, process = tx_process } +installplugin { name = "group",        reset = gr_reset, process = gr_process } +installplugin { name = "graphictext",  reset = gt_reset, process = gt_process } +installplugin { name = "shade",        reset = sh_reset, process = sh_process } +installplugin { name = "bitmap",       reset = bm_reset, process = bm_process } +installplugin { name = "box",          reset = bx_reset, process = bx_process } +installplugin { name = "position",     reset = ps_reset, process = ps_process } +installplugin { name = "figure",       reset = fg_reset, process = fg_process } +installplugin { name = "layer",        reset = la_reset, process = la_process } +installplugin { name = "transparency", reset = tr_reset, process = tr_process } diff --git a/tex/context/base/mkxl/mlib-pps.mkxl b/tex/context/base/mkxl/mlib-pps.mkxl index d010d9991..61313afa2 100644 --- a/tex/context/base/mkxl/mlib-pps.mkxl +++ b/tex/context/base/mkxl/mlib-pps.mkxl @@ -15,7 +15,7 @@  \unprotect -\registerctxluafile{mlib-pps}{} +\registerctxluafile{mlib-pps}{autosuffix}  %D Todo: catch nested graphics like external figures with dummies. diff --git a/tex/context/base/mkxl/mlib-run.lmt b/tex/context/base/mkxl/mlib-run.lmt new file mode 100644 index 000000000..602d6f36c --- /dev/null +++ b/tex/context/base/mkxl/mlib-run.lmt @@ -0,0 +1,681 @@ +if not modules then modules = { } end modules ['mlib-run'] = { +    version   = 1.001, +    comment   = "companion to mlib-ctx.mkiv", +    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL", +    copyright = "PRAGMA ADE / ConTeXt Development Team", +    license   = "see context related readme files", +} + +-- cmyk       -> done, native +-- spot       -> done, but needs reworking (simpler) +-- multitone  -> +-- shade      -> partly done, todo: cm +-- figure     -> done +-- hyperlink  -> low priority, easy + +-- new * run +-- or +-- new * execute^1 * finish + +-- a*[b,c] == b + a * (c-b) + +--[[ldx-- +<p>The directional helpers and pen analysis are more or less translated from the +<l n='c'/> code. It really helps that Taco know that source so well. Taco and I spent +quite some time on speeding up the <l n='lua'/> and <l n='c'/> code. There is not +much to gain, especially if one keeps in mind that when integrated in <l n='tex'/> +only a part of the time is spent in <l n='metapost'/>. Of course an integrated +approach is way faster than an external <l n='metapost'/> and processing time +nears zero.</p> +--ldx]]-- + +local type, tostring, tonumber, next = type, tostring, tonumber, next +local find, striplines = string.find, utilities.strings.striplines +local concat, insert, remove = table.concat, table.insert, table.remove + +local emptystring = string.is_empty + +local trace_graphics   = false  trackers.register("metapost.graphics",   function(v) trace_graphics   = v end) +local trace_tracingall = false  trackers.register("metapost.tracingall", function(v) trace_tracingall = v end) + +local report_metapost = logs.reporter("metapost") +local texerrormessage = logs.texerrormessage + +local starttiming     = statistics.starttiming +local stoptiming      = statistics.stoptiming + +local formatters      = string.formatters + +local mplib           = mplib +metapost              = metapost or { } +local metapost        = metapost + +metapost.showlog      = false +metapost.lastlog      = "" +metapost.texerrors    = false +metapost.exectime     = metapost.exectime or { } -- hack +metapost.nofruns      = 0 + +local mpxformats      = { } +local nofformats      = 0 +local mpxpreambles    = { } +local mpxterminals    = { } +local mpxextradata    = { } + +-- The flatten hack is needed because the library currently barks on \n\n and the +-- collapse because mp cannot handle snippets due to grouping issues. + +-- todo: pass tables to executempx instead of preparing beforehand, +-- as it's more efficient for the terminal + +local function flatten(source,target) +    for i=1,#source do +        local d = source[i] +        if type(d) == "table" then +            flatten(d,target) +        elseif d and d ~= "" then +            target[#target+1] = d +        end +    end +    return target +end + +local function prepareddata(data) +    if data and data ~= "" then +        if type(data) == "table" then +            data = flatten(data,{ }) +            data = #data > 1 and concat(data,"\n") or data[1] +        end +        return data +    end +end + +local function executempx(mpx,data) +    local terminal = mpxterminals[mpx] +    if terminal then +        terminal.writer(data) +        data = "" +    elseif type(data) == "table" then +        data = prepareddata(data,collapse) +    end +    metapost.nofruns = metapost.nofruns + 1 +    return mpx:execute(data) +end + +directives.register("mplib.texerrors",  function(v) metapost.texerrors = v end) +trackers.register  ("metapost.showlog", function(v) metapost.showlog   = v end) + +function metapost.resetlastlog() +    metapost.lastlog = "" +end + +local new_instance = mplib.new +local find_file    = mplib.finder + +function metapost.reporterror(result) +    if not result then +        report_metapost("error: no result object returned") +        return true +    elseif result.status == 0 then +        return false +    elseif mplib.realtimelogging then +        return false -- we already reported +    else +        local t = result.term +        local e = result.error +        local l = result.log +        local report = metapost.texerrors and texerrormessage or report_metapost +        if t and t ~= "" then +            report("mp error: %s",striplines(t)) +        end +        if e == "" or e == "no-error" then +            e = nil +        end +        if e then +            report("mp error: %s",striplines(e)) +        end +        if not t and not e and l then +            metapost.lastlog = metapost.lastlog .. "\n" .. l +            report_metapost("log: %s",l) +        else +            report_metapost("error: unknown, no error, terminal or log messages") +        end +        return true +    end +end + +local f_preamble = formatters [ [[ +    boolean mplib ; mplib := true ; +    let dump = endinput ; +    input "%s" ; +    randomseed:=%s; +]] ] + +local methods = { +    double  = "double", +    scaled  = "scaled", + -- binary  = "binary", +    binary  = "double", +    decimal = "decimal", +    default = "scaled", +} + +function metapost.runscript(code) +    return "" +end + +function metapost.scripterror(str) +    report_metapost("script error: %s",str) +end + +-- todo: random_seed + +local seed = nil + +function metapost.load(name,method) +    starttiming(mplib) +    if not seed then +        seed = job.getrandomseed() +        if seed <= 1 then +            seed = seed % 1000 +        elseif seed > 4095 then +            seed = seed % 4096 +        end +    end +    method = method and methods[method] or "scaled" +    local mpx, terminal = new_instance { +        ini_version  = true, +        math_mode    = method, +        run_script   = metapost.runscript, +        script_error = metapost.scripterror, +        make_text    = metapost.maketext, +        extensions   = 1, +     -- random_seed  = seed, +        utf8_mode    = true, +        text_mode    = true, +    } +    report_metapost("initializing number mode %a",method) +    local result +    if not mpx then +        result = { status = 99, error = "out of memory"} +    else +        mpxterminals[mpx] = terminal +        -- pushing permits advanced features +        metapost.pushscriptrunner(mpx) +        result = executempx(mpx,f_preamble(file.addsuffix(name,"mp"),seed)) +        metapost.popscriptrunner() +    end +    stoptiming(mplib) +    metapost.reporterror(result) +    return mpx, result +end + +function metapost.checkformat(mpsinput,method) +    local mpsinput  = mpsinput or "metafun" +    local foundfile = "" +    if file.suffix(mpsinput) ~= "" then +        foundfile  = find_file(mpsinput) or "" +    end + -- if foundfile == "" then + --     foundfile  = find_file(file.replacesuffix(mpsinput,"mpvi")) or "" + -- end +    if CONTEXTLMTXMODE > 0 and foundfile == "" then +        foundfile  = find_file(file.replacesuffix(mpsinput,"mpxl")) or "" +    end +    if foundfile == "" then +        foundfile  = find_file(file.replacesuffix(mpsinput,"mpiv")) or "" +    end +    if foundfile == "" then +        foundfile  = find_file(file.replacesuffix(mpsinput,"mp")) or "" +    end +    if foundfile == "" then +        report_metapost("loading %a fails, format not found",mpsinput) +    else +        report_metapost("loading %a as %a using method %a",mpsinput,foundfile,method or "default") +        local mpx, result = metapost.load(foundfile,method) +        if mpx then +            return mpx +        else +            report_metapost("error in loading %a",mpsinput) +            metapost.reporterror(result) +        end +    end +end + +function metapost.unload(mpx) +    starttiming(mplib) +    if mpx then +        mpx:finish() +    end +    stoptiming(mplib) +end + +metapost.defaultformat   = "metafun" +metapost.defaultinstance = "metafun" +metapost.defaultmethod   = "default" + +function metapost.getextradata(mpx) +    return mpxextradata[mpx] +end + +function metapost.pushformat(specification,f,m) -- was: instance, name, method +    if type(specification) ~= "table" then +        specification = { +            instance = specification, +            format   = f, +            method   = m, +        } +    end +    local instance    = specification.instance +    local format      = specification.format +    local method      = specification.method +    local definitions = specification.definitions +    local extensions  = specification.extensions +    local preamble    = nil +    if not instance or instance == "" then +        instance = metapost.defaultinstance +        specification.instance = instance +    end +    if not format or format == "" then +        format = metapost.defaultformat +        specification.format = format +    end +    if not method or method == "" then +        method = metapost.defaultmethod +        specification.method = method +    end +    if definitions and definitions ~= "" then +        preamble = definitions +    end +    if extensions and extensions ~= "" then +        if preamble then +            preamble = preamble .. "\n" .. extensions +        else +            preamble = extensions +        end +    end +    nofformats = nofformats + 1 +    local usedinstance = instance .. ":" .. nofformats +    local mpx = mpxformats  [usedinstance] +    local mpp = mpxpreambles[instance] or "" + -- report_metapost("push instance %a (%S)",usedinstance,mpx) +    if preamble then +        preamble = prepareddata(preamble) +        mpp = mpp .. "\n" .. preamble +        mpxpreambles[instance] = mpp +    end +    if not mpx then +        report_metapost("initializing instance %a using format %a and method %a",usedinstance,format,method) +        mpx = metapost.checkformat(format,method) +        mpxformats  [usedinstance] = mpx +        mpxextradata[mpx] = { } +        if mpp ~= "" then +            preamble = mpp +        end +    end +    if preamble then +        executempx(mpx,preamble) +    end +    specification.mpx = mpx +    return mpx +end + +-- luatex.wrapup(function() +--     for k, mpx in next, mpxformats do +--         mpx:finish() +--     end +-- end) + +function metapost.popformat() +    nofformats = nofformats - 1 +end + +function metapost.reset(mpx) +    if not mpx then +        -- nothing +    elseif type(mpx) == "string" then +        if mpxformats[mpx] then +            local instance = mpxformats[mpx] +            instance:finish() +            mpxterminals[mpx] = nil +            mpxextradata[mpx] = nil +            mpxformats  [mpx] = nil +        end +    else +        for name, instance in next, mpxformats do +            if instance == mpx then +                mpx:finish() +                mpxterminals[mpx] = nil +                mpxextradata[mpx] = nil +                mpxformats  [mpx] = nil +                break +            end +        end +    end +end + +local mp_tra = { } +local mp_tag = 0 + +-- key/values + +do + +    local stack, top = { }, nil + +    function metapost.setvariable(k,v) +        if top then +            top[k] = v +        else +            metapost.variables[k] = v +        end +    end + +    function metapost.pushvariable(k) +        local t = { } +        if top then +            insert(stack,top) +            top[k] = t +        else +            metapost.variables[k] = t +        end +        top = t +    end + +    function metapost.popvariable() +        top = remove(stack) +    end + +    local stack = { } + +    function metapost.pushvariables() +        insert(stack,metapost.variables) +        metapost.variables = { } +    end + +    function metapost.popvariables() +        metapost.variables = remove(stack) or metapost.variables +    end + +end + + +if not metapost.process then + +    function metapost.process(specification) +        metapost.run(specification) +    end + +end + +-- run, process, convert and flush all work with a specification with the +-- following (often optional) fields +-- +--     mpx          string or mp object +--     data         string or table of strings +--     flusher      table with flush methods +--     askedfig     string ("all" etc) or number +--     incontext    boolean +--     plugmode     boolean + +local function makebeginbanner(specification) +    return formatters["%% begin graphic: n=%s\n\n"](metapost.n) +end + +local function makeendbanner(specification) +    return "\n% end graphic\n\n" +end + +function metapost.run(specification) +    local mpx       = specification.mpx +    local data      = specification.data +    local converted = false +    local result    = { } +    local mpxdone   = type(mpx) == "string" +    if mpxdone then +        mpx = metapost.pushformat { instance = mpx, format = mpx } +    end +    if mpx and data then +        local tra = nil +        starttiming(metapost) -- why not at the outer level ... +        metapost.variables = { } -- todo also push / pop +        metapost.pushscriptrunner(mpx) +        if trace_graphics then +            tra = mp_tra[mpx] +            if not tra then +                mp_tag = mp_tag + 1 +                local jobname = tex.jobname +                tra = { +                    inp = io.open(formatters["%s-mplib-run-%03i.mp"] (jobname,mp_tag),"w"), +                    log = io.open(formatters["%s-mplib-run-%03i.log"](jobname,mp_tag),"w"), +                } +                mp_tra[mpx] = tra +            end +            local banner = makebeginbanner(specification) +            tra.inp:write(banner) +            tra.log:write(banner) +        end +        local function process(d,i) +            if d then +                if trace_graphics then +                    if i then +                        tra.inp:write(formatters["\n%% begin snippet %s\n"](i)) +                    end +                    if type(d) == "table" then +                        for i=1,#d do +                            tra.inp:write(d[i]) +                        end +                    else +                        tra.inp:write(d) +                    end +                    if i then +                        tra.inp:write(formatters["\n%% end snippet %s\n"](i)) +                    end +                end +                starttiming(metapost.exectime) +                result = executempx(mpx,d) +                stoptiming(metapost.exectime) +                if trace_graphics and result then +                    local str = result.log or result.error +                    if str and str ~= "" then +                        tra.log:write(str) +                    end +                end +                if not metapost.reporterror(result) then +                    if metapost.showlog then +                        -- make function and overload in lmtx +                        local str = result.term ~= "" and result.term or "no terminal output" +                        if not emptystring(str) then +                            metapost.lastlog = metapost.lastlog .. "\n" .. str +                            report_metapost("log: %s",str) +                        end +                    end +                    if result.fig then +                        converted = metapost.convert(specification,result) +                    end +                end +            elseif i then +                report_metapost("error: invalid graphic component %s",i) +            else +                report_metapost("error: invalid graphic") +            end +        end + +--         local data = prepareddata(data) +        if type(data) == "table" then +            if trace_tracingall then +                executempx(mpx,"tracingall;") +            end +                process(data) +--             for i=1,#data do +--                 process(data[i],i) +--             end +        else +            if trace_tracingall then +                data = "tracingall;" .. data +            end +            process(data) +        end +        if trace_graphics then +            local banner = makeendbanner(specification) +            tra.inp:write(banner) +            tra.log:write(banner) +        end +        stoptiming(metapost) +        metapost.popscriptrunner(mpx) +    end +    if mpxdone then +        metapost.popformat() +    end +    return converted, result +end + +if not metapost.convert then + +    function metapost.convert() +        report_metapost('warning: no converter set') +    end + +end + +-- This will be redone as we no longer output svg of ps! + +-- function metapost.directrun(formatname,filename,outputformat,astable,mpdata) +--     local fullname = file.addsuffix(filename,"mp") +--     local data = mpdata or io.loaddata(fullname) +--     if outputformat ~= "svg" then +--         outputformat = "mps" +--     end +--     if not data then +--         report_metapost("unknown file %a",filename) +--     else +--         local mpx = metapost.checkformat(formatname) +--         if not mpx then +--             report_metapost("unknown format %a",formatname) +--         else +--             report_metapost("processing %a",(mpdata and (filename or "data")) or fullname) +--             local result = executempx(mpx,data) +--             if not result then +--                 report_metapost("error: no result object returned") +--             elseif result.status > 0 then +--                 report_metapost("error: %s",(result.term or "no-term") .. "\n" .. (result.error or "no-error")) +--             else +--                 if metapost.showlog then +--                     metapost.lastlog = metapost.lastlog .. "\n" .. result.term +--                     report_metapost("info: %s",result.term or "no-term") +--                 end +--                 local figures = result.fig +--                 if figures then +--                     local sorted = table.sortedkeys(figures) +--                     if astable then +--                         local result = { } +--                         report_metapost("storing %s figures in table",#sorted) +--                         for k=1,#sorted do +--                             local v = sorted[k] +--                             if outputformat == "mps" then +--                                 result[v] = figures[v]:postscript() +--                             else +--                                 result[v] = figures[v]:svg() -- (3) for prologues +--                             end +--                         end +--                         return result +--                     else +--                         local basename = file.removesuffix(file.basename(filename)) +--                         for k=1,#sorted do +--                             local v = sorted[k] +--                             local output +--                             if outputformat == "mps" then +--                                 output = figures[v]:postscript() +--                             else +--                                 output = figures[v]:svg() -- (3) for prologues +--                             end +--                             local outname = formatters["%s-%s.%s"](basename,v,outputformat) +--                             report_metapost("saving %s bytes in %a",#output,outname) +--                             io.savedata(outname,output) +--                         end +--                         return #sorted +--                     end +--                 end +--             end +--         end +--     end +-- end + +function metapost.directrun(formatname,filename,outputformat,astable,mpdata) +    report_metapost("producing postscript and svg is no longer supported") +end + +do + +    local result = { } +    local width  = 0 +    local height = 0 +    local depth  = 0 +    local bbox   = { 0, 0, 0, 0 } + +    local flusher = { +        startfigure = function(n,llx,lly,urx,ury) +            result = { } +            width  = urx - llx +            height = ury +            depth  = -lly +            bbox   = { llx, lly, urx, ury } +        end, +        flushfigure = function(t) +            local r = #result +            for i=1,#t do +                r = r + 1 +                result[r] = t[i] +            end +        end, +        stopfigure = function() +        end, +    } + +    -- make table variant: + +    function metapost.simple(instance,code,useextensions,dontwrap) +        -- can we pickup the instance ? +        local mpx = metapost.pushformat { +            instance = instance or "simplefun", +            format   = "metafun", -- or: minifun +            method   = "double", +        } +        metapost.process { +            mpx        = mpx, +            flusher    = flusher, +            askedfig   = 1, +            useplugins = useextensions, +            data       = dontwrap and { code } or { "beginfig(1);", code, "endfig;" }, +            incontext  = false, +        } +        metapost.popformat() +        if result then +            local stream = concat(result," ") +            result = { } -- nil -- cleanup .. weird, we can have a dangling q +            return stream, width, height, depth, bbox +        else +            return "", 0, 0, 0, { 0, 0, 0, 0 } +        end +    end + +end + +local getstatistics = mplib.getstatistics or mplib.statistics + +function metapost.getstatistics(memonly) +    if memonly then +        local n, m = 0, 0 +        for name, mpx in next, mpxformats do +            n = n + 1 +            m = m + getstatistics(mpx).memory +        end +        return n, m +    else +        local t = { } +        for name, mpx in next, mpxformats do +            t[name] = getstatistics(mpx) +        end +        return t +    end +end diff --git a/tex/context/base/mkxl/mlib-scn.lmt b/tex/context/base/mkxl/mlib-scn.lmt index ff1cff0e3..64e5d9763 100644 --- a/tex/context/base/mkxl/mlib-scn.lmt +++ b/tex/context/base/mkxl/mlib-scn.lmt @@ -33,14 +33,8 @@ local insert, remove = table.insert, table.remove  local mplib    = mplib  local metapost = metapost -local codes = mplib.getcodes() -local types = mplib.gettypes() - -table.hashed(codes) -table.hashed(types) - -metapost.codes = codes -metapost.types = types +local codes = metapost.codes +local types = metapost.types  local setmetatableindex   = table.setmetatableindex @@ -676,21 +670,23 @@ function metapost.scanparameters()      return get_parameters()  end -metapost.registerscript("getparameters",       getparameters) -metapost.registerscript("applyparameters",     applyparameters) -metapost.registerscript("presetparameters",    presetparameters) -metapost.registerscript("hasparameter",        hasparameter) -metapost.registerscript("hasoption",           hasoption) -metapost.registerscript("getparameter",        getparameter) -metapost.registerscript("getparameterdefault", getparameterdefault) -metapost.registerscript("getparametercount",   getparametercount) -metapost.registerscript("getmaxparametercount",getmaxparametercount) -metapost.registerscript("getparameterpath",    getparameterpath) -metapost.registerscript("getparameterpen",     getparameterpen) -metapost.registerscript("getparametertext",    getparametertext) ---------.registerscript("getparameteroption",  getparameteroption) -metapost.registerscript("pushparameters",      pushparameters) -metapost.registerscript("popparameters",       popparameters) +local registerscript = metapost.registerscript + +registerscript("getparameters",       getparameters) +registerscript("applyparameters",     applyparameters) +registerscript("presetparameters",    presetparameters) +registerscript("hasparameter",        hasparameter) +registerscript("hasoption",           hasoption) +registerscript("getparameter",        getparameter) +registerscript("getparameterdefault", getparameterdefault) +registerscript("getparametercount",   getparametercount) +registerscript("getmaxparametercount",getmaxparametercount) +registerscript("getparameterpath",    getparameterpath) +registerscript("getparameterpen",     getparameterpen) +registerscript("getparametertext",    getparametertext) +--------------("getparameteroption",  getparameteroption) +registerscript("pushparameters",      pushparameters) +registerscript("popparameters",       popparameters)  function metapost.getparameter(list)      local n = #list @@ -716,7 +712,7 @@ end  -- goodies -metapost.registerscript("definecolor", function() +registerscript("definecolor", function()      scantoken() -- we scan the semicolon      local s = get_parameters()      attributes.colors.defineprocesscolordirect(s) diff --git a/tex/generic/context/luatex/luatex-fonts-merged.lua b/tex/generic/context/luatex/luatex-fonts-merged.lua index 846b397cc..2f4c0175e 100644 --- a/tex/generic/context/luatex/luatex-fonts-merged.lua +++ b/tex/generic/context/luatex/luatex-fonts-merged.lua @@ -1,6 +1,6 @@  -- merged file : c:/data/develop/context/sources/luatex-fonts-merged.lua  -- parent file : c:/data/develop/context/sources/luatex-fonts.lua --- merge date  : 2020-12-08 18:41 +-- merge date  : 2020-12-09 10:48  do -- begin closure to overcome local limits and interference | 
