mlib-ctx.lmt /size: 11 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['mlib-ctx'] = {
2    version   = 1.001,
3    comment   = "companion to mlib-ctx.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files",
7}
8
9local type, tostring = type, tostring
10local format, concat = string.format, table.concat
11local settings_to_hash = utilities.parsers.settings_to_hash
12local formatters = string.formatters
13
14local report_metapost = logs.reporter ("metapost")
15local status_metapost = logs.messenger("metapost")
16
17local starttiming     = statistics.starttiming
18local stoptiming      = statistics.stoptiming
19
20local trace_graphic   = false
21
22trackers.register("metapost.graphics",
23    function(v) trace_graphic = v end
24);
25
26local mplib            = mplib
27
28metapost               = metapost or { }
29local metapost         = metapost
30local context          = context
31
32local setters          = tokens.setters
33local setmacro         = setters.macro
34local implement        = interfaces.implement
35
36local v_no             <const> = interfaces.variables.no
37
38local extensiondata    = metapost.extensiondata or storage.allocate { }
39metapost.extensiondata = extensiondata
40
41storage.register("metapost/extensiondata",extensiondata,"metapost.extensiondata")
42
43function metapost.setextensions(instances,data)
44    if data and data ~= "" then
45        extensiondata[#extensiondata+1] = {
46            usedinall  = not instances or instances == "",
47            instances  = settings_to_hash(instances or ""),
48            extensions = data,
49        }
50    end
51end
52
53function metapost.getextensions(instance,state)
54    if state and state == v_no then
55        return ""
56    else
57        local t = { }
58        for i=1,#extensiondata do
59            local e = extensiondata[i]
60            local status = e.instances[instance]
61            if (status ~= true) and (e.usedinall or status) then
62                t[#t+1] = e.extensions
63                e.instances[instance] = true
64            end
65        end
66        return concat(t," ")
67    end
68end
69
70implement {
71    name      = "setmpextensions",
72    actions   = metapost.setextensions,
73    arguments = "2 strings",
74}
75
76implement {
77    name      = "getmpextensions",
78    actions   = { metapost.getextensions, context } ,
79    arguments = "string"
80}
81
82local patterns = {
83    "meta-imp-%s.mkxl",
84    "meta-imp-%s.mkiv",
85    "meta-imp-%s.tex",
86    -- obsolete:
87    "meta-%s.mkiv",
88    "meta-%s.tex"
89}
90
91local function action(name,foundname)
92    commands.loadlibrary(name,foundname,false)
93    status_metapost("library %a is loaded",name)
94end
95
96local function failure(name)
97    report_metapost("library %a is unknown or invalid",name)
98end
99
100implement {
101    name      = "useMPlibrary",
102 -- arguments = "string",
103    arguments = "optional",
104    public    = true,
105    protected = true,
106    actions   = function(name)
107        resolvers.uselibrary {
108            name     = name,
109            patterns = patterns,
110            action   = action,
111            failure  = failure,
112            onlyonce = true,
113        }
114    end
115}
116
117-- metapost.variables = { } -- to be stacked
118
119implement {
120    name      = "mprunvar",
121    arguments = "string",
122    actions   = function(name)
123        local value = metapost.variables[name]
124        local tvalue = type(value)
125        if tvalue == "nil" then
126            context("0")
127        elseif tvalue == "table" then
128            context(concat(value," "))
129        elseif tvalue == "number" or tvalue == "boolean" then
130            context(tostring(value))
131        elseif tvalue == "string" then
132            context(value)
133        end
134    end
135}
136
137implement {
138    name      = "mpruntab",
139    arguments = { "string", "integer" },
140    actions   = function(name,n)
141        local value = metapost.variables[name]
142        if value ~= nil then
143            local tvalue = type(value)
144            if tvalue == "table" then
145                context(value[n])
146            elseif tvalue == "number" or tvalue == "boolean" then
147                context(tostring(value))
148            elseif tvalue == "string" then
149                context(value)
150            end
151        end
152    end
153}
154
155implement {
156    name      = "mprunset",
157    arguments = "2 strings",
158    actions   = function(name,connector)
159        local value = metapost.variables[name]
160        if value ~= nil then
161            local tvalue = type(value)
162            if tvalue == "table" then
163                context(concat(value,connector))
164            elseif tvalue == "number" or tvalue == "boolean" then
165                context(tostring(value))
166            elseif tvalue == "string" then
167                context(value)
168            end
169        end
170    end
171}
172
173-- we need to move more from pps to here as pps is the plugin .. the order is a mess
174-- or just move the scanners to pps
175
176function metapost.graphic(specification)
177    metapost.pushformat(specification)
178    metapost.graphic_base_pass(specification)
179    metapost.popformat()
180end
181
182function metapost.startgraphic(t)
183    if not t then
184        t = { }
185    end
186    if not t.instance then
187        t.instance = metapost.defaultinstance
188    end
189    if not t.format then
190        t.format = metapost.defaultformat
191    end
192    if not t.method then
193        t.method = metapost.defaultmethod
194    end
195    t.data = { }
196    return t
197end
198
199function metapost.stopgraphic(t)
200    if t then
201        t.data = concat(t.data or { },"\n")
202        if trace_graphic then
203            report_metapost("\n"..t.data.."\n")
204        end
205        metapost.graphic(t)
206    end
207end
208
209function metapost.tographic(t,f,s,...)
210    local d = t.data
211    d[#d+1] = s and formatters[f](s,...) or f
212end
213
214implement {
215    name      = "mpgraphic",
216    actions   = metapost.graphic,
217    arguments = {
218        {
219            { "instance" },
220            { "format" },
221            { "data" },
222            { "initializations" },
223            { "extensions" },
224            { "inclusions" },
225            { "definitions" },
226            { "figure" },
227            { "method" },
228            { "namespace" },
229            { "filtering" },
230        }
231    }
232}
233
234implement {
235    name      = "mpsetoutercolor",
236    actions   = function(...) metapost.setoutercolor(...) end, -- not yet implemented
237    arguments = "4 integers",
238}
239
240-- this has to become a codeinjection
241
242function metapost.getclippath(specification) -- why not a special instance for this
243    local mpx  = metapost.pushformat(specification)
244    local data = specification.data or ""
245    if mpx and data ~= "" then
246        starttiming(metapost)
247        starttiming(metapost.exectime)
248        local result = mpx:execute ( format ( "%s;%s;beginfig(1);%s;%s;endfig;",
249            specification.extensions or "",
250            specification.inclusions or "",
251            specification.initializations or "",
252            data
253        ) )
254        stoptiming(metapost.exectime)
255        if result.status > 0 then
256            report_metapost("%s: %s", result.status, result.error or result.term or result.log)
257            result = nil
258        else
259            result = metapost.filterclippath(result)
260        end
261        stoptiming(metapost)
262        metapost.popformat()
263        return result
264    else
265        metapost.popformat()
266    end
267end
268
269function metapost.filterclippath(result)
270    if result then
271        local figures = result.fig
272        if figures and #figures > 0 then
273            local figure = figures[1]
274            local objects = figure:objects()
275            if objects then
276                local lastclippath
277                for o=1,#objects do
278                    local object = objects[o]
279                    if object.type == "start_clip" then
280                        lastclippath = object.path
281                    end
282                end
283                return lastclippath
284            end
285        end
286    end
287end
288
289function metapost.theclippath(...)
290    local result = metapost.getclippath(...)
291    if result then -- we could just print the table
292        return concat(metapost.flushnormalpath(result)," ")
293    else
294        return ""
295    end
296end
297
298implement {
299    name      = "mpsetclippath",
300    actions   = function(specification)
301        local p = specification.data and metapost.theclippath(specification)
302        if not p or p == "" then
303            local b = number.dimenfactors.bp
304            local w = b * (specification.width or 0)
305            local h = b * (specification.height or 0)
306            p = formatters["0 0 m %.6N 0 l %.6N %.6N l 0 %.6N l"](w,w,h,h)
307        end
308        setmacro("MPclippath",p,"global")
309    end,
310    arguments = {
311        {
312            { "instance" },
313            { "format" },
314            { "data" },
315            { "initializations" },
316            { "useextensions" },
317            { "inclusions" },
318            { "method" },
319            { "namespace" },
320            { "filtering" },
321            { "width", "dimension" },
322            { "height", "dimension" },
323        },
324    }
325}
326
327statistics.register("metapost", function()
328    local n = metapost.nofruns
329    if n and n > 0 then
330        local elapsedtime = statistics.elapsedtime
331        local elapsed     = statistics.elapsed
332        local runs, stats = metapost.nofscriptruns()
333        local instances,
334              memory      = metapost.getstatistics(true)
335        return format("%s seconds, loading: %s, execution: %s, n: %s, average: %s, instances: %i, luacalls: %s, memory: %0.3f M",
336            elapsedtime(metapost), elapsedtime(mplib), elapsedtime(metapost.exectime), n,
337            elapsedtime((elapsed(metapost) + elapsed(mplib) + elapsed(metapost.exectime)) / n),
338            instances, stats and stats or runs, memory/(1024*1024))
339    else
340        return nil
341    end
342end)
343
344-- only used in graphictexts
345
346metapost.tex = metapost.tex or { }
347local mptex  = metapost.tex
348
349local environments = { }
350
351function mptex.set(str)
352    environments[#environments+1] = str
353end
354
355function mptex.setfrombuffer(name)
356    environments[#environments+1] = buffers.getcontent(name)
357end
358
359function mptex.get()
360    return concat(environments,"\n")
361end
362
363function mptex.reset()
364    environments = { }
365end
366
367implement {
368    name      = "mptexset",
369    arguments = "string",
370    actions   = mptex.set
371}
372
373implement {
374    name      = "mptexsetfrombuffer",
375    arguments = "string",
376    actions   = mptex.setfrombuffer
377}
378
379implement {
380    name      = "mptexget",
381    actions   = { mptex.get, context }
382}
383
384implement {
385    name      = "mptexreset",
386    actions   = mptex.reset
387}
388
389-- moved from mlib-lua:
390
391mp = mp or {  -- system namespace
392    set    = { },
393    get    = { },
394    aux    = { },
395    scan   = { },
396    skip   = { },
397    inject = { },
398}
399
400MP = MP or { } -- user namespace
401
402-- We had this:
403--
404--   table.setmetatablecall(mp,function(t,k) mpprint(k) end)
405--
406-- but the next one is more interesting because we cannot use calls like:
407--
408--   lua.mp.somedefdname("foo")
409--
410-- which is due to expansion of somedefdname during suffix creation. So:
411--
412--   lua.mp("somedefdname","foo")
413
414table.setmetatablecall(mp,function(t,k,...) return t[k](...) end)
415table.setmetatablecall(MP,function(t,k,...) return t[k](...) end)
416
417-- A secret option:
418
419implement {
420    name      = "resetMPinstance",
421    protected = true,
422    public    = true,
423    arguments = "optional",
424    actions   = function(s)
425        if s and s ~= "" then
426            report_metapost("resetting instance %a",s)
427            metapost.reset(s)
428        end
429    end,
430}
431