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.pdf Binary files differindex 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.pdf Binary files differindex 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 |