mlib-ctx.lmt /size: 11 Kb    last modification: 2023-12-21 09:44
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             = 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    actions   = function(name)
104        resolvers.uselibrary {
105            name     = name,
106            patterns = patterns,
107            action   = action,
108            failure  = failure,
109            onlyonce = true,
110        }
111    end
112}
113
114-- metapost.variables = { } -- to be stacked
115
116implement {
117    name      = "mprunvar",
118    arguments = "string",
119    actions   = function(name)
120        local value = metapost.variables[name]
121        local tvalue = type(value)
122        if tvalue == "nil" then
123            context("0")
124        elseif tvalue == "table" then
125            context(concat(value," "))
126        elseif tvalue == "number" or tvalue == "boolean" then
127            context(tostring(value))
128        elseif tvalue == "string" then
129            context(value)
130        end
131    end
132}
133
134implement {
135    name      = "mpruntab",
136    arguments = { "string", "integer" },
137    actions   = function(name,n)
138        local value = metapost.variables[name]
139        if value ~= nil then
140            local tvalue = type(value)
141            if tvalue == "table" then
142                context(value[n])
143            elseif tvalue == "number" or tvalue == "boolean" then
144                context(tostring(value))
145            elseif tvalue == "string" then
146                context(value)
147            end
148        end
149    end
150}
151
152implement {
153    name      = "mprunset",
154    arguments = "2 strings",
155    actions   = function(name,connector)
156        local value = metapost.variables[name]
157        if value ~= nil then
158            local tvalue = type(value)
159            if tvalue == "table" then
160                context(concat(value,connector))
161            elseif tvalue == "number" or tvalue == "boolean" then
162                context(tostring(value))
163            elseif tvalue == "string" then
164                context(value)
165            end
166        end
167    end
168}
169
170-- we need to move more from pps to here as pps is the plugin .. the order is a mess
171-- or just move the scanners to pps
172
173function metapost.graphic(specification)
174    metapost.pushformat(specification)
175    metapost.graphic_base_pass(specification)
176    metapost.popformat()
177end
178
179function metapost.startgraphic(t)
180    if not t then
181        t = { }
182    end
183    if not t.instance then
184        t.instance = metapost.defaultinstance
185    end
186    if not t.format then
187        t.format = metapost.defaultformat
188    end
189    if not t.method then
190        t.method = metapost.defaultmethod
191    end
192    t.data = { }
193    return t
194end
195
196function metapost.stopgraphic(t)
197    if t then
198        t.data = concat(t.data or { },"\n")
199        if trace_graphic then
200            report_metapost("\n"..t.data.."\n")
201        end
202        metapost.graphic(t)
203    end
204end
205
206function metapost.tographic(t,f,s,...)
207    local d = t.data
208    d[#d+1] = s and formatters[f](s,...) or f
209end
210
211implement {
212    name      = "mpgraphic",
213    actions   = metapost.graphic,
214    arguments = {
215        {
216            { "instance" },
217            { "format" },
218            { "data" },
219            { "initializations" },
220            { "extensions" },
221            { "inclusions" },
222            { "definitions" },
223            { "figure" },
224            { "method" },
225            { "namespace" },
226            { "filtering" },
227        }
228    }
229}
230
231implement {
232    name      = "mpsetoutercolor",
233    actions   = function(...) metapost.setoutercolor(...) end, -- not yet implemented
234    arguments = "4 integers",
235}
236
237-- this has to become a codeinjection
238
239function metapost.getclippath(specification) -- why not a special instance for this
240    local mpx  = metapost.pushformat(specification)
241    local data = specification.data or ""
242    if mpx and data ~= "" then
243        starttiming(metapost)
244        starttiming(metapost.exectime)
245        local result = mpx:execute ( format ( "%s;%s;beginfig(1);%s;%s;endfig;",
246            specification.extensions or "",
247            specification.inclusions or "",
248            specification.initializations or "",
249            data
250        ) )
251        stoptiming(metapost.exectime)
252        if result.status > 0 then
253            report_metapost("%s: %s", result.status, result.error or result.term or result.log)
254            result = nil
255        else
256            result = metapost.filterclippath(result)
257        end
258        stoptiming(metapost)
259        metapost.popformat()
260        return result
261    else
262        metapost.popformat()
263    end
264end
265
266function metapost.filterclippath(result)
267    if result then
268        local figures = result.fig
269        if figures and #figures > 0 then
270            local figure = figures[1]
271            local objects = figure:objects()
272            if objects then
273                local lastclippath
274                for o=1,#objects do
275                    local object = objects[o]
276                    if object.type == "start_clip" then
277                        lastclippath = object.path
278                    end
279                end
280                return lastclippath
281            end
282        end
283    end
284end
285
286function metapost.theclippath(...)
287    local result = metapost.getclippath(...)
288    if result then -- we could just print the table
289        return concat(metapost.flushnormalpath(result)," ")
290    else
291        return ""
292    end
293end
294
295implement {
296    name      = "mpsetclippath",
297    actions   = function(specification)
298        local p = specification.data and metapost.theclippath(specification)
299        if not p or p == "" then
300            local b = number.dimenfactors.bp
301            local w = b * (specification.width or 0)
302            local h = b * (specification.height or 0)
303            p = formatters["0 0 m %.6N 0 l %.6N %.6N l 0 %.6N l"](w,w,h,h)
304        end
305        setmacro("MPclippath",p,"global")
306    end,
307    arguments = {
308        {
309            { "instance" },
310            { "format" },
311            { "data" },
312            { "initializations" },
313            { "useextensions" },
314            { "inclusions" },
315            { "method" },
316            { "namespace" },
317            { "filtering" },
318            { "width", "dimension" },
319            { "height", "dimension" },
320        },
321    }
322}
323
324statistics.register("metapost", function()
325    local n = metapost.nofruns
326    if n and n > 0 then
327        local elapsedtime = statistics.elapsedtime
328        local elapsed     = statistics.elapsed
329        local runs, stats = metapost.nofscriptruns()
330        local instances,
331              memory      = metapost.getstatistics(true)
332        return format("%s seconds, loading: %s, execution: %s, n: %s, average: %s, instances: %i, luacalls: %s, memory: %0.3f M",
333            elapsedtime(metapost), elapsedtime(mplib), elapsedtime(metapost.exectime), n,
334            elapsedtime((elapsed(metapost) + elapsed(mplib) + elapsed(metapost.exectime)) / n),
335            instances, stats and stats or runs, memory/(1024*1024))
336    else
337        return nil
338    end
339end)
340
341-- only used in graphictexts
342
343metapost.tex = metapost.tex or { }
344local mptex  = metapost.tex
345
346local environments = { }
347
348function mptex.set(str)
349    environments[#environments+1] = str
350end
351
352function mptex.setfrombuffer(name)
353    environments[#environments+1] = buffers.getcontent(name)
354end
355
356function mptex.get()
357    return concat(environments,"\n")
358end
359
360function mptex.reset()
361    environments = { }
362end
363
364implement {
365    name      = "mptexset",
366    arguments = "string",
367    actions   = mptex.set
368}
369
370implement {
371    name      = "mptexsetfrombuffer",
372    arguments = "string",
373    actions   = mptex.setfrombuffer
374}
375
376implement {
377    name      = "mptexget",
378    actions   = { mptex.get, context }
379}
380
381implement {
382    name      = "mptexreset",
383    actions   = mptex.reset
384}
385
386-- moved from mlib-lua:
387
388mp = mp or {  -- system namespace
389    set    = { },
390    get    = { },
391    aux    = { },
392    scan   = { },
393    skip   = { },
394    inject = { },
395}
396
397MP = MP or { } -- user namespace
398
399-- We had this:
400--
401--   table.setmetatablecall(mp,function(t,k) mpprint(k) end)
402--
403-- but the next one is more interesting because we cannot use calls like:
404--
405--   lua.mp.somedefdname("foo")
406--
407-- which is due to expansion of somedefdname during suffix creation. So:
408--
409--   lua.mp("somedefdname","foo")
410
411table.setmetatablecall(mp,function(t,k,...) return t[k](...) end)
412table.setmetatablecall(MP,function(t,k,...) return t[k](...) end)
413
414-- A secret option:
415
416implement {
417    name      = "resetMPinstance",
418    protected = true,
419    public    = true,
420    arguments = "optional",
421    actions   = function(s)
422        if s and s ~= "" then
423            report_metapost("resetting instance %a",s)
424            metapost.reset(s)
425        end
426    end,
427}
428