1if not modules then modules = { } end modules ['mlib-run'] = {
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
9
10
11
12
13
14
15
16
17
18
19
20
21
22
31
32local type, tostring, tonumber, next = type, tostring, tonumber, next
33local find, striplines = string.find, utilities.strings.striplines
34local concat, insert, remove = table.concat, table.insert, table.remove
35
36local emptystring = string.is_empty
37
38local trace_graphics = false trackers.register("metapost.graphics", function(v) trace_graphics = v end)
39local trace_tracingall = false trackers.register("metapost.tracingall", function(v) trace_tracingall = v end)
40
41local report_metapost = logs.reporter("metapost")
42local texerrormessage = logs.texerrormessage
43
44local starttiming = statistics.starttiming
45local stoptiming = statistics.stoptiming
46
47local formatters = string.formatters
48
49local mplib = mplib
50metapost = metapost or { }
51local metapost = metapost
52
53metapost.showlog = false
54metapost.lastlog = ""
55metapost.texerrors = false
56metapost.exectime = metapost.exectime or { }
57metapost.nofruns = 0
58
59local mpxformats = { }
60local nofformats = 0
61local mpxpreambles = { }
62local mpxterminals = { }
63local mpxextradata = { }
64
65
66
67
68
69
70
71local function flatten(source,target)
72 for i=1,#source do
73 local d = source[i]
74 if type(d) == "table" then
75 flatten(d,target)
76 elseif d and d ~= "" then
77 target[#target+1] = d
78 end
79 end
80 return target
81end
82
83local function prepareddata(data)
84 if data and data ~= "" then
85 if type(data) == "table" then
86 data = flatten(data,{ })
87 data = #data > 1 and concat(data,"\n") or data[1]
88 end
89 return data
90 end
91end
92
93local function executempx(mpx,data)
94 local terminal = mpxterminals[mpx]
95 if terminal then
96 terminal.writer(data)
97 data = ""
98 elseif type(data) == "table" then
99 data = prepareddata(data,collapse)
100 end
101 metapost.nofruns = metapost.nofruns + 1
102 return mpx:execute(data)
103end
104
105directives.register("mplib.texerrors", function(v) metapost.texerrors = v end)
106trackers.register ("metapost.showlog", function(v) metapost.showlog = v end)
107
108function metapost.resetlastlog()
109 metapost.lastlog = ""
110end
111
112local new_instance = mplib.new
113local find_file = mplib.finder
114
115function metapost.reporterror(result)
116 if not result then
117 report_metapost("error: no result object returned")
118 return true
119 elseif result.status == 0 then
120 return false
121 elseif mplib.realtimelogging then
122 return false
123 else
124 local t = result.term
125 local e = result.error
126 local l = result.log
127 local report = metapost.texerrors and texerrormessage or report_metapost
128 if t and t ~= "" then
129 report("mp error: %s",striplines(t))
130 end
131 if e == "" or e == "no-error" then
132 e = nil
133 end
134 if e then
135 report("mp error: %s",striplines(e))
136 end
137 if not t and not e and l then
138 metapost.lastlog = metapost.lastlog .. "\n" .. l
139 report_metapost("log: %s",l)
140 else
141 report_metapost("error: unknown, no error, terminal or log messages")
142 end
143 return true
144 end
145end
146
147local f_preamble = formatters [ [[
148 boolean mplib ; mplib := true ;
149 let dump = endinput ;
150 input "%s" ;
151 randomseed:=%s;
152]] ]
153
154local methods = {
155 double = "double",
156 scaled = "scaled",
157
158 binary = "double",
159 decimal = "decimal",
160 default = "scaled",
161}
162
163function metapost.runscript(code)
164 return ""
165end
166
167function metapost.scripterror(str)
168 report_metapost("script error: %s",str)
169end
170
171
172
173local seed = nil
174
175function metapost.load(name,method)
176 starttiming(mplib)
177 if not seed then
178 seed = job.getrandomseed()
179 if seed <= 1 then
180 seed = seed % 1000
181 elseif seed > 4095 then
182 seed = seed % 4096
183 end
184 end
185 method = method and methods[method] or "scaled"
186 local mpx, terminal = new_instance {
187 ini_version = true,
188 math_mode = method,
189 run_script = metapost.runscript,
190 script_error = metapost.scripterror,
191 make_text = metapost.maketext,
192 extensions = 1,
193
194 utf8_mode = true,
195 text_mode = true,
196 }
197 report_metapost("initializing number mode %a",method)
198 local result
199 if not mpx then
200 result = { status = 99, error = "out of memory"}
201 else
202 mpxterminals[mpx] = terminal
203
204 metapost.pushscriptrunner(mpx)
205 result = executempx(mpx,f_preamble(file.addsuffix(name,"mp"),seed))
206 metapost.popscriptrunner()
207 end
208 stoptiming(mplib)
209 metapost.reporterror(result)
210 return mpx, result
211end
212
213function metapost.checkformat(mpsinput,method)
214 local mpsinput = mpsinput or "metafun"
215 local foundfile = ""
216 if file.suffix(mpsinput) ~= "" then
217 foundfile = find_file(mpsinput) or ""
218 end
219
220
221
222 if CONTEXTLMTXMODE > 0 and foundfile == "" then
223 foundfile = find_file(file.replacesuffix(mpsinput,"mpxl")) or ""
224 end
225 if foundfile == "" then
226 foundfile = find_file(file.replacesuffix(mpsinput,"mpiv")) or ""
227 end
228 if foundfile == "" then
229 foundfile = find_file(file.replacesuffix(mpsinput,"mp")) or ""
230 end
231 if foundfile == "" then
232 report_metapost("loading %a fails, format not found",mpsinput)
233 else
234 report_metapost("loading %a as %a using method %a",mpsinput,foundfile,method or "default")
235 local mpx, result = metapost.load(foundfile,method)
236 if mpx then
237 return mpx
238 else
239 report_metapost("error in loading %a",mpsinput)
240 metapost.reporterror(result)
241 end
242 end
243end
244
245function metapost.unload(mpx)
246 starttiming(mplib)
247 if mpx then
248 mpx:finish()
249 end
250 stoptiming(mplib)
251end
252
253metapost.defaultformat = "metafun"
254metapost.defaultinstance = "metafun"
255metapost.defaultmethod = "default"
256
257function metapost.getextradata(mpx)
258 return mpxextradata[mpx]
259end
260
261function metapost.pushformat(specification,f,m)
262 if type(specification) ~= "table" then
263 specification = {
264 instance = specification,
265 format = f,
266 method = m,
267 }
268 end
269 local instance = specification.instance
270 local format = specification.format
271 local method = specification.method
272 local definitions = specification.definitions
273 local extensions = specification.extensions
274 local preamble = nil
275 if not instance or instance == "" then
276 instance = metapost.defaultinstance
277 specification.instance = instance
278 end
279 if not format or format == "" then
280 format = metapost.defaultformat
281 specification.format = format
282 end
283 if not method or method == "" then
284 method = metapost.defaultmethod
285 specification.method = method
286 end
287 if definitions and definitions ~= "" then
288 preamble = definitions
289 end
290 if extensions and extensions ~= "" then
291 if preamble then
292 preamble = preamble .. "\n" .. extensions
293 else
294 preamble = extensions
295 end
296 end
297 nofformats = nofformats + 1
298 local usedinstance = instance .. ":" .. nofformats
299 local mpx = mpxformats [usedinstance]
300 local mpp = mpxpreambles[instance] or ""
301
302 if preamble then
303 preamble = prepareddata(preamble)
304 mpp = mpp .. "\n" .. preamble
305 mpxpreambles[instance] = mpp
306 end
307 if not mpx then
308 report_metapost("initializing instance %a using format %a and method %a",usedinstance,format,method)
309 mpx = metapost.checkformat(format,method)
310 mpxformats [usedinstance] = mpx
311 mpxextradata[mpx] = { }
312 if mpp ~= "" then
313 preamble = mpp
314 end
315 end
316 if preamble then
317 executempx(mpx,preamble)
318 end
319 specification.mpx = mpx
320 return mpx
321end
322
323
324
325
326
327
328
329function metapost.popformat()
330 nofformats = nofformats - 1
331end
332
333function metapost.reset(mpx)
334 if not mpx then
335
336 elseif type(mpx) == "string" then
337 if mpxformats[mpx] then
338 local instance = mpxformats[mpx]
339 instance:finish()
340 mpxterminals[mpx] = nil
341 mpxextradata[mpx] = nil
342 mpxformats [mpx] = nil
343 end
344 else
345 for name, instance in next, mpxformats do
346 if instance == mpx then
347 mpx:finish()
348 mpxterminals[mpx] = nil
349 mpxextradata[mpx] = nil
350 mpxformats [mpx] = nil
351 break
352 end
353 end
354 end
355end
356
357local mp_tra = { }
358local mp_tag = 0
359
360
361
362do
363
364 local stack, top = { }, nil
365
366 function metapost.setvariable(k,v)
367 if top then
368 top[k] = v
369 else
370 metapost.variables[k] = v
371 end
372 end
373
374 function metapost.pushvariable(k)
375 local t = { }
376 if top then
377 insert(stack,top)
378 top[k] = t
379 else
380 metapost.variables[k] = t
381 end
382 top = t
383 end
384
385 function metapost.popvariable()
386 top = remove(stack)
387 end
388
389 local stack = { }
390
391 function metapost.pushvariables()
392 insert(stack,metapost.variables)
393 metapost.variables = { }
394 end
395
396 function metapost.popvariables()
397 metapost.variables = remove(stack) or metapost.variables
398 end
399
400end
401
402
403if not metapost.process then
404
405 function metapost.process(specification)
406 metapost.run(specification)
407 end
408
409end
410
411
412
413
414
415
416
417
418
419
420
421local function makebeginbanner(specification)
422 return formatters["%% begin graphic: n=%s\n\n"](metapost.n)
423end
424
425local function makeendbanner(specification)
426 return "\n% end graphic\n\n"
427end
428
429function metapost.run(specification)
430 local mpx = specification.mpx
431 local data = specification.data
432 local converted = false
433 local result = { }
434 local mpxdone = type(mpx) == "string"
435 if mpxdone then
436 mpx = metapost.pushformat { instance = mpx, format = mpx }
437 end
438 if mpx and data then
439 local tra = nil
440 starttiming(metapost)
441 metapost.variables = { }
442 metapost.pushscriptrunner(mpx)
443 if trace_graphics then
444 tra = mp_tra[mpx]
445 if not tra then
446 mp_tag = mp_tag + 1
447 local jobname = tex.jobname
448 tra = {
449 inp = io.open(formatters["%s-mplib-run-%03i.mp"] (jobname,mp_tag),"w"),
450 log = io.open(formatters["%s-mplib-run-%03i.log"](jobname,mp_tag),"w"),
451 }
452 mp_tra[mpx] = tra
453 end
454 local banner = makebeginbanner(specification)
455 tra.inp:write(banner)
456 tra.log:write(banner)
457 end
458 local function process(d,i)
459 if d then
460 if trace_graphics then
461 if i then
462 tra.inp:write(formatters["\n%% begin snippet %s\n"](i))
463 end
464 if type(d) == "table" then
465 for i=1,#d do
466 tra.inp:write(d[i])
467 end
468 else
469 tra.inp:write(d)
470 end
471 if i then
472 tra.inp:write(formatters["\n%% end snippet %s\n"](i))
473 end
474 end
475 starttiming(metapost.exectime)
476 result = executempx(mpx,d)
477 stoptiming(metapost.exectime)
478 if trace_graphics and result then
479 local str = result.log or result.error
480 if str and str ~= "" then
481 tra.log:write(str)
482 end
483 end
484 if not metapost.reporterror(result) then
485 if metapost.showlog then
486
487 local str = result.term ~= "" and result.term or "no terminal output"
488 if not emptystring(str) then
489 metapost.lastlog = metapost.lastlog .. "\n" .. str
490 report_metapost("log: %s",str)
491 end
492 end
493 if result.fig then
494 converted = metapost.convert(specification,result)
495 end
496 end
497 elseif i then
498 report_metapost("error: invalid graphic component %s",i)
499 else
500 report_metapost("error: invalid graphic")
501 end
502 end
503
504
505 if type(data) == "table" then
506 if trace_tracingall then
507 executempx(mpx,"tracingall;")
508 end
509 process(data)
510
511
512
513 else
514 if trace_tracingall then
515 data = "tracingall;" .. data
516 end
517 process(data)
518 end
519 if trace_graphics then
520 local banner = makeendbanner(specification)
521 tra.inp:write(banner)
522 tra.log:write(banner)
523 end
524 stoptiming(metapost)
525 metapost.popscriptrunner(mpx)
526 end
527 if mpxdone then
528 metapost.popformat()
529 end
530 return converted, result
531end
532
533if not metapost.convert then
534
535 function metapost.convert()
536 report_metapost('warning: no converter set')
537 end
538
539end
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604function metapost.directrun(formatname,filename,outputformat,astable,mpdata)
605 report_metapost("producing postscript and svg is no longer supported")
606end
607
608do
609
610 local result = { }
611 local width = 0
612 local height = 0
613 local depth = 0
614 local bbox = { 0, 0, 0, 0 }
615
616 local flusher = {
617 startfigure = function(n,llx,lly,urx,ury)
618 result = { }
619 width = urx - llx
620 height = ury
621 depth = -lly
622 bbox = { llx, lly, urx, ury }
623 end,
624 flushfigure = function(t)
625 local r = #result
626 for i=1,#t do
627 r = r + 1
628 result[r] = t[i]
629 end
630 end,
631 stopfigure = function()
632 end,
633 }
634
635
636
637 function metapost.simple(instance,code,useextensions,dontwrap)
638
639 local mpx = metapost.pushformat {
640 instance = instance or "simplefun",
641 format = "metafun",
642 method = "double",
643 }
644 metapost.process {
645 mpx = mpx,
646 flusher = flusher,
647 askedfig = 1,
648 useplugins = useextensions,
649 data = dontwrap and { code } or { "beginfig(1);", code, "endfig;" },
650 incontext = false,
651 }
652 metapost.popformat()
653 if result then
654 local stream = concat(result," ")
655 result = { }
656 return stream, width, height, depth, bbox
657 else
658 return "", 0, 0, 0, { 0, 0, 0, 0 }
659 end
660 end
661
662end
663
664local getstatistics = mplib.getstatistics or mplib.statistics
665
666function metapost.getstatistics(memonly)
667 if memonly then
668 local n, m = 0, 0
669 for name, mpx in next, mpxformats do
670 n = n + 1
671 m = m + getstatistics(mpx).memory
672 end
673 return n, m
674 else
675 local t = { }
676 for name, mpx in next, mpxformats do
677 t[name] = getstatistics(mpx)
678 end
679 return t
680 end
681end
682 |